Skip to content

Conversation

@akshaydeo
Copy link
Contributor

@akshaydeo akshaydeo commented Dec 26, 2025

Summary

Add configurable header filtering for extra headers forwarded to LLM providers via the x-bf-eh-* prefix. This allows administrators to control which headers are allowed to pass through to providers using allowlist and denylist mechanisms.

Changes

  • Added GlobalHeaderFilterConfig struct with allowlist and denylist fields
  • Integrated header filtering into the context conversion process
  • Added API endpoints for getting and updating header filter configuration
  • Created UI for managing header filter settings with clear documentation
  • Updated hash generation to include header filter configuration
  • Implemented filter logic with proper precedence rules:
    • If allowlist is non-empty, only headers in allowlist are forwarded
    • If denylist is non-empty, headers in denylist are blocked
    • If both are configured, allowlist takes precedence first, then denylist filters
    • If neither is configured, all headers pass through (except security-sensitive ones)

Type of change

  • Feature
  • Bug fix
  • Refactor
  • Documentation
  • Chore/CI

Affected areas

  • Core (Go)
  • Transports (HTTP)
  • Providers/Integrations
  • Plugins
  • UI (Next.js)
  • Docs

How to test

  1. Start Bifrost server
  2. Navigate to the new Header Filter page in the UI (under Config)
  3. Add headers to allowlist/denylist and save changes
  4. Test API requests with x-bf-eh-* headers to verify filtering works:
# Test with a header that should be allowed
curl -X POST http://localhost:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "x-bf-eh-allowed-header: test-value" \
  -d '{"model": "gpt-3.5-turbo", "messages": [{"role": "user", "content": "Hello"}]}'

# Test with a header that should be blocked
curl -X POST http://localhost:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "x-bf-eh-blocked-header: test-value" \
  -d '{"model": "gpt-3.5-turbo", "messages": [{"role": "user", "content": "Hello"}]}'

Screenshots/Recordings

N/A

Breaking changes

  • Yes
  • No

Related issues

N/A

Security considerations

  • Security-sensitive headers are always blocked regardless of configuration
  • The implementation normalizes header names to lowercase for case-insensitive matching
  • The feature helps prevent unintended header leakage to third-party providers

Checklist

  • I read docs/contributing/README.md and followed the guidelines
  • I added/updated tests where appropriate
  • I updated documentation where needed
  • I verified builds succeed (Go and UI)
  • I verified the CI pipeline passes locally if applicable

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 26, 2025

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Summary by CodeRabbit

Release Notes

  • New Features

    • Added header filtering configuration with allowlist and denylist for custom headers forwarded to LLM providers
    • New UI controls in workspace settings to manage allowed and blocked headers
    • Built-in security protections prevent sensitive headers from being misconfigured
  • Chores

    • Version updates across core, framework, and plugin components

✏️ Tip: You can customize this high-level summary in your review settings.

Walkthrough

This PR introduces a global header filtering configuration feature that allows users to control which custom headers (x-bf-eh-* prefix) are forwarded to LLM providers via allowlist and denylist arrays. The configuration is persisted in the database, validated through HTTP handlers, and applied during request context conversion.

Changes

Cohort / File(s) Summary
Config Store Data Structures
framework/configstore/tables/config.go, framework/configstore/clientconfig.go
Added new GlobalHeaderFilterConfig struct with Allowlist and Denylist fields; added HeaderFilterConfig field to ClientConfig; included config hashing support in GenerateClientConfigHash
Database Persistence
framework/configstore/tables/clientconfig.go, framework/configstore/rdb.go, framework/configstore/migrations.go
Added HeaderFilterConfigJSON column to TableClientConfig with serialization/deserialization in BeforeSave/AfterFind hooks; propagated field through GetClientConfig/UpdateClientConfig; created database migration to add the new column
HTTP API & Validation
transports/bifrost-http/handlers/config.go
Extended ConfigManager interface with ReloadHeaderFilterConfig method; integrated header filter config into updateConfig with validation and security checks; added validateHeaderFilterConfig to prevent security headers in allowlist/denylist; added headerFilterConfigEqual helper for change detection
Handler & Router Integration
transports/bifrost-http/handlers/inference.go, transports/bifrost-http/handlers/mcp.go, transports/bifrost-http/integrations/router.go
Updated ConvertToBifrostContext call sites to pass headerFilterConfig as third parameter across multiple HTTP handlers
Test Mocks
transports/bifrost-http/integrations/bedrock_test.go
Added GetHeaderFilterConfig() method to mock HandlerStore to support testing
Library Integration
transports/bifrost-http/lib/config.go, transports/bifrost-http/lib/ctx.go
Extended HandlerStore interface with GetHeaderFilterConfig() getter; updated ConvertToBifrostContext signature to accept header filter config; integrated filtering logic with shouldAllowHeader predicate, security header enforcement, and forwarding decision logic
Server Runtime
transports/bifrost-http/server/server.go
Added ReloadHeaderFilterConfig method to ServerCallbacks interface and BifrostHTTPServer implementation for in-memory config updates with logging
Schemas & API Contracts
transports/config.schema.json, ui/lib/types/config.ts, ui/lib/types/schemas.ts
Added header_filter_config schema property with allowlist and denylist array fields; created TypeScript interfaces and Zod schemas for type safety
UI Configuration Component
ui/app/workspace/config/views/clientSettingsView.tsx
Added header forwarding configuration UI with Accordion sections for allowlist/denylist, input fields, add/remove handlers, security header detection, and change detection logic
Documentation & Versioning
core/changelog.md, core/version, framework/version, transports/changelog.md, transports/version, plugins/*/changelog.md, plugins/*/version
Updated changelogs documenting allowlist/denylist header forwarding support; incremented versions: core 1.2.42→1.2.43, framework 1.1.52→1.1.53, transports 1.3.53→1.3.54, plugins 1.3.5x→1.3.5x or 1.4.53→1.4.54

Sequence Diagram(s)

sequenceDiagram
    participant User as User/UI
    participant Handler as HTTP Handler
    participant ConfigStore as Config Store
    participant DB as Database
    participant Server as Server
    participant Request as Request Handler
    
    User->>Handler: POST /api/config with header_filter_config
    
    rect rgb(200, 220, 255)
    Note over Handler: Validation Phase
    Handler->>Handler: validateHeaderFilterConfig()
    alt Invalid (security headers detected)
        Handler-->>User: HTTP 400 Bad Request
    end
    end
    
    rect rgb(200, 255, 220)
    Note over ConfigStore,DB: Persistence Phase
    Handler->>ConfigStore: UpdateClientConfig(config)
    ConfigStore->>ConfigStore: Serialize HeaderFilterConfig → JSON
    ConfigStore->>DB: Store header_filter_config_json
    DB-->>ConfigStore: Confirm
    end
    
    rect rgb(255, 240, 200)
    Note over Server: Reload Phase
    Handler->>Server: ReloadHeaderFilterConfig(config)
    Server->>Server: Update in-memory ClientConfig.HeaderFilterConfig
    Server-->>Handler: Success
    end
    
    Handler-->>User: HTTP 200 OK
    
    rect rgb(255, 220, 220)
    Note over Request: Subsequent Request (Header Filtering)
    Request->>Request: ConvertToBifrostContext(ctx, headerFilterConfig)
    alt Allowlist configured
        Request->>Request: shouldAllowHeader() checks allowlist first
    else Denylist configured
        Request->>Request: shouldAllowHeader() filters by denylist
    else No config
        Request->>Request: Default security headers enforced
    end
    Request->>Request: Forward filtered headers to provider
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Suggested reviewers

  • danpiths
  • Pratham-Mishra04

🐰 A filter so fine, headers aligned,
Allow and deny, perfectly designed,
Through config they flow with care,
Security headers stay held there,
Custom headers now have their fair share! 🎀

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 71.43% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'adds allowlist and denylist for headers' directly describes the main feature addition in the changeset.
Description check ✅ Passed The PR description includes all required template sections: Summary, Changes, Type of change, Affected areas, How to test, Security considerations, and Checklist with confirmation of guidelines compliance.

📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8a88ccf and e593d42.

📒 Files selected for processing (39)
  • core/changelog.md
  • core/version
  • framework/changelog.md
  • framework/configstore/clientconfig.go
  • framework/configstore/migrations.go
  • framework/configstore/rdb.go
  • framework/configstore/tables/clientconfig.go
  • framework/configstore/tables/config.go
  • framework/version
  • plugins/governance/changelog.md
  • plugins/governance/version
  • plugins/jsonparser/changelog.md
  • plugins/jsonparser/version
  • plugins/logging/changelog.md
  • plugins/logging/version
  • plugins/maxim/changelog.md
  • plugins/maxim/version
  • plugins/mocker/changelog.md
  • plugins/mocker/version
  • plugins/otel/changelog.md
  • plugins/otel/version
  • plugins/semanticcache/changelog.md
  • plugins/semanticcache/version
  • plugins/telemetry/changelog.md
  • plugins/telemetry/version
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/integrations/bedrock_test.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/lib/ctx.go
  • transports/bifrost-http/server/server.go
  • transports/changelog.md
  • transports/config.schema.json
  • transports/version
  • ui/app/workspace/config/views/clientSettingsView.tsx
  • ui/lib/types/config.ts
  • ui/lib/types/schemas.ts

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor Author

akshaydeo commented Dec 26, 2025

@akshaydeo akshaydeo marked this pull request as ready for review December 26, 2025 08:15
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (6)
transports/bifrost-http/lib/config.go (1)

39-41: Expose header filter via HandlerStore; consider future-proofing concurrent access

Wiring GetHeaderFilterConfig through HandlerStore and implementing it on Config by returning c.ClientConfig.HeaderFilterConfig gives the router/context code clean read-only access, and mirrors the existing ShouldAllowDirectKeys pattern.

If you expect frequent runtime reloads of the header filter config, it may be worth eventually backing HeaderFilterConfig with an atomic.Pointer or guarding writes with the existing mutex to keep the race detector quiet while still allowing cheap reads from hot paths. Not a blocker given the existing pattern, just something to consider.

Also applies to: 2065-2071

transports/config.schema.json (1)

84-103: Clarify header_filter_config header name format (case and prefix).

Runtime filtering (in ConvertToBifrostContext) lowercases header names and strips the x-bf-eh- prefix before checking allowlist/denylist, but this schema’s descriptions don’t say that. If users configure "User-Agent" or include the prefix, those entries will never match the normalized "user-agent" label, and filtering will appear ineffective.

Consider either:

  • documenting explicitly that entries must be lowercase header names without the x-bf-eh- prefix (e.g. "user-agent"), or
  • normalizing the configured allowlist/denylist to lowercase at load time so configuration is case-insensitive.

This will avoid subtle misconfigurations and align user expectations with the actual filter behavior.

ui/lib/types/schemas.ts (1)

680-691: Consider normalizing and deduplicating header names in the Zod schemas.

The schemas correctly mirror the backend shape, but you may want to harden UX here:

  • Normalize to lowercase so config is case-insensitive and matches the runtime behavior that already lowercases header names before filtering.
  • Optionally deduplicate entries to avoid redundant values.

For example:

export const globalHeaderFilterConfigSchema = z.object({
  allowlist: z
    .array(z.string().min(1))
    .transform(list => Array.from(new Set(list.map(h => h.toLowerCase()))))
    .optional(),
  denylist: z
    .array(z.string().min(1))
    .transform(list => Array.from(new Set(list.map(h => h.toLowerCase()))))
    .optional(),
});

This keeps the API the same but makes misconfiguration much harder.

Also applies to: 709-710

transports/bifrost-http/handlers/inference.go (1)

449-451: Header filter wiring into ConvertToBifrostContext is correct but quite repetitive.

All the updated call sites correctly pass h.handlerStore.ShouldAllowDirectKeys() and h.config.GetHeaderFilterConfig() into ConvertToBifrostContext, so behavior-wise this looks good.

Given how often this pattern appears, consider adding a small helper on CompletionHandler, e.g.:

func (h *CompletionHandler) bifrostContext(ctx *fasthttp.RequestCtx) (*context.Context, context.CancelFunc) {
	return lib.ConvertToBifrostContext(ctx, h.handlerStore.ShouldAllowDirectKeys(), h.config.GetHeaderFilterConfig())
}

and then reusing it across handlers. That will make future changes to context creation (additional flags, config, etc.) much easier and less error-prone.

Also applies to: 573-574, 649-650, 731-732, 803-804, 879-880, 1010-1011, 1096-1097, 1395-1396, 1447-1449, 1487-1488, 1526-1527, 1565-1566, 1647-1648, 1706-1707, 1745-1746, 1784-1785, 1823-1824

transports/bifrost-http/handlers/config.go (1)

648-701: Consider deduplicating and filtering empty entries server-side.

While the frontend filters empty strings before submission, the backend should also validate to ensure data integrity. Additionally, consider deduplicating entries in the allowlist/denylist to prevent user confusion.

🔎 Proposed enhancement for input sanitization
 	// Normalize header names to lowercase for case-insensitive matching
+	seen := make(map[string]bool)
+	var cleanedAllowlist []string
 	for i, header := range payload.Allowlist {
-		payload.Allowlist[i] = strings.ToLower(strings.TrimSpace(header))
+		normalized := strings.ToLower(strings.TrimSpace(header))
+		if normalized != "" && !seen[normalized] {
+			cleanedAllowlist = append(cleanedAllowlist, normalized)
+			seen[normalized] = true
+		}
 	}
+	payload.Allowlist = cleanedAllowlist
+
+	seen = make(map[string]bool)
+	var cleanedDenylist []string
 	for i, header := range payload.Denylist {
-		payload.Denylist[i] = strings.ToLower(strings.TrimSpace(header))
+		normalized := strings.ToLower(strings.TrimSpace(header))
+		if normalized != "" && !seen[normalized] {
+			cleanedDenylist = append(cleanedDenylist, normalized)
+			seen[normalized] = true
+		}
 	}
+	payload.Denylist = cleanedDenylist
ui/app/workspace/config/views/headerFilterView.tsx (1)

30-47: Consider improving type safety for useFieldArray.

The as never type casts suggest a type mismatch between the form schema and useFieldArray expectations. This is a common pattern when using useFieldArray with primitive arrays (string[]) rather than object arrays.

For better type safety, you could wrap the header strings in objects:

// In schema:
allowlist: z.array(z.object({ value: z.string() })).optional()

// In useFieldArray:
name: "allowlist" // No cast needed

However, since this works correctly at runtime and the current approach is common with react-hook-form, this is a minor improvement that can be deferred.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6445a81 and ba75398.

📒 Files selected for processing (20)
  • framework/configstore/clientconfig.go
  • framework/configstore/rdb.go
  • framework/configstore/store.go
  • framework/configstore/tables/config.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/integrations/bedrock_test.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/lib/ctx.go
  • transports/bifrost-http/server/server.go
  • transports/config.schema.json
  • ui/app/workspace/config/header-filter/page.tsx
  • ui/app/workspace/config/views/headerFilterView.tsx
  • ui/components/sidebar.tsx
  • ui/lib/store/apis/baseApi.ts
  • ui/lib/store/apis/configApi.ts
  • ui/lib/types/config.ts
  • ui/lib/types/schemas.ts
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

always check the stack if there is one for the current PR. do not give localized reviews for the PR, always see all changes in the light of the whole stack of PRs (if there is a stack, if there is no stack you can continue to make localized suggestions/reviews)

Files:

  • ui/app/workspace/config/header-filter/page.tsx
  • transports/config.schema.json
  • ui/lib/types/config.ts
  • ui/lib/types/schemas.ts
  • transports/bifrost-http/server/server.go
  • ui/components/sidebar.tsx
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/integrations/bedrock_test.go
  • transports/bifrost-http/lib/ctx.go
  • framework/configstore/clientconfig.go
  • framework/configstore/store.go
  • ui/app/workspace/config/views/headerFilterView.tsx
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/integrations/router.go
  • framework/configstore/tables/config.go
  • transports/bifrost-http/handlers/mcp.go
  • framework/configstore/rdb.go
  • ui/lib/store/apis/configApi.ts
  • ui/lib/store/apis/baseApi.ts
🧠 Learnings (2)
📚 Learning: 2025-12-09T17:07:42.007Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/schemas/account.go:9-18
Timestamp: 2025-12-09T17:07:42.007Z
Learning: In core/schemas/account.go, the HuggingFaceKeyConfig field within the Key struct is currently unused and reserved for future Hugging Face inference endpoint deployments. Do not flag this field as missing from OpenAPI documentation or require its presence in the API spec until the feature is actively implemented and used. When the feature is added, update the OpenAPI docs accordingly; otherwise, treat this field as non-breaking and not part of the current API surface.

Applied to files:

  • transports/bifrost-http/server/server.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/integrations/bedrock_test.go
  • transports/bifrost-http/lib/ctx.go
  • framework/configstore/clientconfig.go
  • framework/configstore/store.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/integrations/router.go
  • framework/configstore/tables/config.go
  • transports/bifrost-http/handlers/mcp.go
  • framework/configstore/rdb.go
📚 Learning: 2025-12-12T08:25:02.629Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1000
File: transports/bifrost-http/integrations/router.go:709-712
Timestamp: 2025-12-12T08:25:02.629Z
Learning: In transports/bifrost-http/**/*.go, update streaming response handling to align with OpenAI Responses API: use typed SSE events such as response.created, response.output_text.delta, response.done, etc., and do not rely on the legacy data: [DONE] termination marker. Note that data: [DONE] is only used by the older Chat Completions and Text Completions streaming APIs. Ensure parsers, writers, and tests distinguish SSE events from the [DONE] sentinel and handle each event type accordingly for correct stream termination and progress updates.

Applied to files:

  • transports/bifrost-http/server/server.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/integrations/bedrock_test.go
  • transports/bifrost-http/lib/ctx.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/handlers/mcp.go
🧬 Code graph analysis (13)
ui/app/workspace/config/header-filter/page.tsx (1)
ui/app/workspace/config/views/headerFilterView.tsx (1)
  • HeaderFilterView (18-257)
ui/lib/types/config.ts (1)
framework/configstore/tables/config.go (1)
  • GlobalHeaderFilterConfig (46-49)
transports/bifrost-http/server/server.go (3)
framework/configstore/tables/config.go (1)
  • GlobalHeaderFilterConfig (46-49)
ui/lib/types/config.ts (1)
  • GlobalHeaderFilterConfig (304-307)
framework/configstore/clientconfig.go (1)
  • ClientConfig (36-51)
transports/bifrost-http/handlers/config.go (2)
framework/configstore/tables/config.go (1)
  • GlobalHeaderFilterConfig (46-49)
ui/lib/types/config.ts (1)
  • GlobalHeaderFilterConfig (304-307)
transports/bifrost-http/integrations/bedrock_test.go (2)
framework/configstore/tables/config.go (1)
  • GlobalHeaderFilterConfig (46-49)
ui/lib/types/config.ts (1)
  • GlobalHeaderFilterConfig (304-307)
transports/bifrost-http/lib/ctx.go (2)
framework/configstore/tables/config.go (1)
  • GlobalHeaderFilterConfig (46-49)
ui/lib/types/config.ts (1)
  • GlobalHeaderFilterConfig (304-307)
framework/configstore/clientconfig.go (2)
framework/configstore/tables/config.go (1)
  • GlobalHeaderFilterConfig (46-49)
ui/lib/types/config.ts (1)
  • GlobalHeaderFilterConfig (304-307)
framework/configstore/store.go (2)
framework/configstore/tables/config.go (1)
  • GlobalHeaderFilterConfig (46-49)
ui/lib/types/config.ts (1)
  • GlobalHeaderFilterConfig (304-307)
transports/bifrost-http/handlers/inference.go (1)
transports/bifrost-http/lib/ctx.go (1)
  • ConvertToBifrostContext (80-369)
transports/bifrost-http/integrations/router.go (1)
transports/bifrost-http/lib/ctx.go (1)
  • ConvertToBifrostContext (80-369)
framework/configstore/tables/config.go (4)
core/providers/gemini/types.go (1)
  • Type (783-783)
ui/lib/types/config.ts (1)
  • GlobalProxyType (269-269)
core/network/http.go (1)
  • GlobalProxyType (46-46)
core/schemas/provider.go (1)
  • NoProxy (147-147)
transports/bifrost-http/handlers/mcp.go (1)
transports/bifrost-http/lib/ctx.go (1)
  • ConvertToBifrostContext (80-369)
ui/lib/store/apis/configApi.ts (2)
framework/configstore/tables/config.go (1)
  • GlobalHeaderFilterConfig (46-49)
ui/lib/types/config.ts (1)
  • GlobalHeaderFilterConfig (304-307)
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
🔇 Additional comments (27)
ui/lib/store/apis/baseApi.ts (1)

138-150: HeaderFilterConfig RTK Query tag wiring looks correct

Adding "HeaderFilterConfig" to tagTypes is consistent with the new config endpoints and should allow proper cache invalidation with no behavioral regressions.

ui/components/sidebar.tsx (1)

3-34: New “Header Filter” config nav entry is consistent and well-scoped

Importing Filter and adding the “Header Filter” sub-item under Config, gated by hasSettingsAccess and routed to /workspace/config/header-filter, aligns with existing sidebar patterns and RBAC usage.

Also applies to: 532-538

transports/bifrost-http/lib/config.go (1)

385-388: HeaderFilterConfig load/merge and persistence pipeline is coherent

The way you:

  • merge HeaderFilterConfig with DB-precedence in mergeClientConfig,
  • sync any file-/client-derived HeaderFilterConfig into the store in loadClientConfigFromFile, and
  • then hydrate ClientConfig.HeaderFilterConfig from the store via loadDefaultHeaderFilterConfig (for both file-backed and default paths)

keeps a single source of truth in the config store while still supporting initial config.json defaults and the “no store” mode. This matches how other config sections are handled.

Also applies to: 501-507, 548-559, 1578-1580, 1763-1773

transports/bifrost-http/integrations/bedrock_test.go (1)

9-10: mockHandlerStore correctly updated for new HandlerStore contract

Extending mockHandlerStore with a headerFilterConfig field and GetHeaderFilterConfig implementation, plus the added tables import, cleanly aligns the tests with the new lib.HandlerStore interface without changing existing test behavior (nil config → default filtering).

Also applies to: 17-19, 25-27

transports/bifrost-http/integrations/router.go (1)

392-399: Router now passes headerFilterConfig into ConvertToBifrostContext as intended

Using g.handlerStore.GetHeaderFilterConfig() alongside ShouldAllowDirectKeys() when calling ConvertToBifrostContext cleanly threads the global header filtering rules into every integration route without touching downstream handler logic.

ui/app/workspace/config/header-filter/page.tsx (1)

1-11: HeaderFilterPage wiring is minimal and consistent with other config pages

Client-side HeaderFilterPage simply wraps HeaderFilterView in the usual max-w-7xl layout and lives at the path referenced by the new sidebar item, which is exactly what’s needed here.

framework/configstore/clientconfig.go (1)

37-51: ClientConfig field and hash integration for HeaderFilterConfig look consistent.

The new HeaderFilterConfig field and its inclusion in GenerateClientConfigHash are wired correctly: allowlist/denylist are sorted before hashing for determinism, and nil vs empty lists intentionally have no hash impact, matching their identical runtime behavior.

No changes needed here.

Also applies to: 144-170

transports/bifrost-http/handlers/mcp.go (1)

53-73: MCP tool handler correctly adopts header filter–aware context.

Using ConvertToBifrostContext(ctx, false, h.store.GetHeaderFilterConfig()) keeps MCP requests opt-out of direct keys while still applying the global header filter configuration. This is consistent with the rest of the stack and doesn’t introduce new behavior quirks.

Looks good as-is.

framework/configstore/store.go (1)

122-129: ConfigStore interface extension for header filter config is consistent; ensure all impls are updated.

Adding GetHeaderFilterConfig / UpdateHeaderFilterConfig here fits the existing pattern for global config (proxy, restart, auth, etc.) and matches the tables.GlobalHeaderFilterConfig type.

Just make sure every concrete ConfigStore implementation and any mocks (e.g., sqlite/postgres stores, in-memory test doubles) implement these methods so the stack compiles and tests continue to pass.

ui/lib/types/config.ts (1)

302-313: TS config types for global header filter match backend and defaults correctly.

GlobalHeaderFilterConfig and DefaultGlobalHeaderFilterConfig align with tables.GlobalHeaderFilterConfig, and wiring it into BifrostConfig via header_filter_config?: GlobalHeaderFilterConfig maintains backward compatibility (field is optional) while exposing the new capability to the UI.

No changes needed here.

Also applies to: 321-329

framework/configstore/rdb.go (1)

1952-1981: LGTM! Methods follow established patterns.

The GetHeaderFilterConfig and UpdateHeaderFilterConfig methods are consistent with existing governance config methods like GetProxyConfig and GetRestartRequiredConfig. The error handling, nil checks, and JSON marshalling patterns align well with the codebase conventions.

framework/configstore/tables/config.go (2)

40-49: LGTM! Well-documented struct with clear filter logic.

The GlobalHeaderFilterConfig struct is well-designed with clear documentation explaining the filter precedence logic. The JSON tags with omitempty allow for flexible partial configuration.


12-12: LGTM!

The constant follows the established naming convention for governance config keys.

transports/bifrost-http/server/server.go (2)

841-856: LGTM! Clean hot-reload implementation.

The method correctly stores the header filter config in memory and provides useful debug logging. Unlike proxy configuration, header filter changes take effect immediately without requiring a restart, which is appropriate since the filter is applied per-request.


66-66: LGTM!

The interface extension is consistent with the existing callback pattern.

ui/lib/store/apis/configApi.ts (2)

90-106: LGTM! RTK Query endpoints follow established patterns.

The new endpoints correctly mirror the existing proxy config pattern. The cache invalidation strategy invalidating both HeaderFilterConfig and Config tags ensures the UI stays synchronized when header filter settings change.


124-125: The hook name useUpdateHeaderFilterConfigMutation is correct and consistent throughout the codebase. No typo exists.

Likely an incorrect or invalid review comment.

transports/bifrost-http/handlers/config.go (2)

626-645: LGTM! Handler follows established patterns.

The getHeaderFilterConfig handler correctly returns empty arrays when config is nil, providing a consistent response structure for the frontend.


163-169: LGTM!

Header filter config is correctly included in the aggregated config response, consistent with how proxy config is handled.

ui/app/workspace/config/views/headerFilterView.tsx (4)

49-59: LGTM! Proper form synchronization with fetched data.

The useEffect correctly resets the form when server data is fetched, ensuring arrays are properly initialized even if the server returns undefined values.


61-73: LGTM! Clean data sanitization before submission.

The onSubmit handler correctly filters out empty strings before sending to the API, preventing invalid entries from being persisted.


114-126: LGTM! Clear and informative filter precedence documentation.

The alert clearly explains the filter logic to users, which matches the implementation in the backend. This is excellent UX design.


239-251: LGTM! Security transparency.

Displaying the list of always-blocked headers provides good transparency to users about the security measures in place.

transports/bifrost-http/lib/ctx.go (4)

100-116: LGTM! Security denylist is comprehensive.

The security denylist correctly blocks sensitive headers that could be exploited:

  • Authentication headers (authorization, proxy-authorization, cookie)
  • Connection headers (host, content-length, connection, transfer-encoding)
  • API key headers that could override governance controls

118-145: LGTM! Filter logic implementation is correct.

The shouldAllowHeader function correctly implements the documented precedence:

  1. Nil config → allow all
  2. Allowlist check (if present) → must be in list
  3. Denylist check (if present) → must not be in list
  4. Both empty → allow all

The implementation matches the documentation in GlobalHeaderFilterConfig and the UI explanation.


287-294: LGTM! Filter application is in the correct order.

The security denylist is checked first (always enforced), followed by the configurable filter. This ensures security-sensitive headers cannot be forwarded even if mistakenly added to an allowlist.


80-80: All callers have been properly updated. All 21 call sites across router.go, mcp.go, and inference.go pass the required three arguments: context, allowDirectKeys flag, and headerFilterConfig.

@akshaydeo akshaydeo force-pushed the 12-26-adds_allowlist_and_denylist_for_headers branch from ba75398 to a65444e Compare December 26, 2025 12:08
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
ui/app/workspace/config/views/clientSettingsView.tsx (3)

30-45: Consider order-insensitive comparison for header filter config.

The current comparison is order-sensitive, meaning ["a", "b"] and ["b", "a"] are considered different. While this works for tracking local edits, it could cause false "unsaved changes" detection if the server returns headers in a different order than stored locally.

🔎 Proposed order-insensitive comparison
 function headerFilterConfigEqual (a?: GlobalHeaderFilterConfig, b?: GlobalHeaderFilterConfig): boolean {
   const aAllowlist = a?.allowlist || []
   const bAllowlist = b?.allowlist || []
   const aDenylist = a?.denylist || []
   const bDenylist = b?.denylist || []

   if (aAllowlist.length !== bAllowlist.length || aDenylist.length !== bDenylist.length) {
     return false
   }

+  const sortedAAllow = [...aAllowlist].sort()
+  const sortedBAllow = [...bAllowlist].sort()
+  const sortedADeny = [...aDenylist].sort()
+  const sortedBDeny = [...bDenylist].sort()
+
   return (
-    aAllowlist.every((v, i) => v === bAllowlist[i]) &&
-    aDenylist.every((v, i) => v === bDenylist[i])
+    sortedAAllow.every((v, i) => v === sortedBAllow[i]) &&
+    sortedADeny.every((v, i) => v === sortedBDeny[i])
   )
 }

84-99: Consider adding a defensive check before accessing bifrostConfig.

The non-null assertion bifrostConfig! on line 94 is technically safe due to the button's disabled logic, but an explicit guard makes the code more robust against future refactoring.

🔎 Proposed defensive check
   const handleSave = useCallback(async () => {
+    if (!bifrostConfig) return
     try {
       // Clean up empty strings from header filter config
       const cleanedConfig = {
         ...localConfig,
         header_filter_config: {
           allowlist: (localConfig.header_filter_config?.allowlist || []).filter((h) => h && h.trim().length > 0),
           denylist: (localConfig.header_filter_config?.denylist || []).filter((h) => h && h.trim().length > 0),
         },
       }
-      await updateCoreConfig({ ...bifrostConfig!, client_config: cleanedConfig }).unwrap()
+      await updateCoreConfig({ ...bifrostConfig, client_config: cleanedConfig }).unwrap()
       toast.success("Client settings updated successfully.")
     } catch (error) {
       toast.error(getErrorMessage(error))
     }
   }, [bifrostConfig, localConfig, updateCoreConfig])

258-278: Using index as key for dynamic lists may cause subtle issues.

When items are removed from the middle of the list, React may preserve DOM state incorrectly because keys shift. For controlled inputs this usually works, but could cause issues with focus or animations. Consider using a stable identifier if problems arise.

One approach would be to generate unique IDs when adding headers:

// Instead of storing string[], store { id: string, value: string }[]
// Then use key={item.id} in the map

For now, since these are simple controlled inputs and the list is typically small, this is acceptable.

Also applies to: 302-322

transports/bifrost-http/handlers/config.go (1)

25-35: Header filter config wiring is correct; consider order-insensitive equality

The new pieces here hang together well:

  • ConfigManager gains ReloadHeaderFilterConfig, and updateConfig invokes it only when the client’s HeaderFilterConfig actually differs.
  • getConfig exposes header_filter_config from the store alongside existing config sections, which is what the UI and API need.

One behavioral nuance: headerFilterConfigEqual uses slices.Equal directly, so allowlist/denylist comparisons are order‑sensitive and treat nil vs non‑nil GlobalHeaderFilterConfig as different even when both lists are empty. Since the filter semantics are set‑like (order doesn’t matter, and “no headers configured” vs “empty lists” behave the same in ConvertToBifrostContext), you might want to normalize before comparison (e.g., sort and/or treat empty configs as equivalent) to avoid unnecessary reloads when only list ordering changes.

If you’re fine with “any structural difference triggers a reload,” the current implementation is otherwise sound.

Also applies to: 161-167, 176-176, 188-192, 223-225, 276-285, 633-642

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ba75398 and a65444e.

📒 Files selected for processing (16)
  • framework/configstore/clientconfig.go
  • framework/configstore/rdb.go
  • framework/configstore/store.go
  • framework/configstore/tables/config.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/integrations/bedrock_test.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/lib/ctx.go
  • transports/bifrost-http/server/server.go
  • transports/config.schema.json
  • ui/app/workspace/config/views/clientSettingsView.tsx
  • ui/lib/types/config.ts
  • ui/lib/types/schemas.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • framework/configstore/tables/config.go
  • framework/configstore/rdb.go
  • transports/bifrost-http/lib/config.go
  • ui/lib/types/config.ts
  • transports/bifrost-http/integrations/bedrock_test.go
  • transports/config.schema.json
  • ui/lib/types/schemas.ts
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

always check the stack if there is one for the current PR. do not give localized reviews for the PR, always see all changes in the light of the whole stack of PRs (if there is a stack, if there is no stack you can continue to make localized suggestions/reviews)

Files:

  • framework/configstore/clientconfig.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/lib/ctx.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/integrations/router.go
  • ui/app/workspace/config/views/clientSettingsView.tsx
  • framework/configstore/store.go
  • transports/bifrost-http/server/server.go
🧠 Learnings (2)
📚 Learning: 2025-12-09T17:07:42.007Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/schemas/account.go:9-18
Timestamp: 2025-12-09T17:07:42.007Z
Learning: In core/schemas/account.go, the HuggingFaceKeyConfig field within the Key struct is currently unused and reserved for future Hugging Face inference endpoint deployments. Do not flag this field as missing from OpenAPI documentation or require its presence in the API spec until the feature is actively implemented and used. When the feature is added, update the OpenAPI docs accordingly; otherwise, treat this field as non-breaking and not part of the current API surface.

Applied to files:

  • framework/configstore/clientconfig.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/lib/ctx.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/integrations/router.go
  • framework/configstore/store.go
  • transports/bifrost-http/server/server.go
📚 Learning: 2025-12-12T08:25:02.629Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1000
File: transports/bifrost-http/integrations/router.go:709-712
Timestamp: 2025-12-12T08:25:02.629Z
Learning: In transports/bifrost-http/**/*.go, update streaming response handling to align with OpenAI Responses API: use typed SSE events such as response.created, response.output_text.delta, response.done, etc., and do not rely on the legacy data: [DONE] termination marker. Note that data: [DONE] is only used by the older Chat Completions and Text Completions streaming APIs. Ensure parsers, writers, and tests distinguish SSE events from the [DONE] sentinel and handle each event type accordingly for correct stream termination and progress updates.

Applied to files:

  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/lib/ctx.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/server/server.go
🧬 Code graph analysis (7)
transports/bifrost-http/handlers/mcp.go (1)
transports/bifrost-http/lib/ctx.go (1)
  • ConvertToBifrostContext (80-369)
transports/bifrost-http/handlers/config.go (3)
framework/configstore/tables/config.go (1)
  • GlobalHeaderFilterConfig (46-49)
ui/lib/types/config.ts (1)
  • GlobalHeaderFilterConfig (304-307)
framework/configstore/clientconfig.go (1)
  • ClientConfig (36-51)
transports/bifrost-http/lib/ctx.go (2)
framework/configstore/tables/config.go (1)
  • GlobalHeaderFilterConfig (46-49)
ui/lib/types/config.ts (1)
  • GlobalHeaderFilterConfig (304-307)
transports/bifrost-http/handlers/inference.go (1)
transports/bifrost-http/lib/ctx.go (1)
  • ConvertToBifrostContext (80-369)
transports/bifrost-http/integrations/router.go (1)
transports/bifrost-http/lib/ctx.go (1)
  • ConvertToBifrostContext (80-369)
ui/app/workspace/config/views/clientSettingsView.tsx (4)
ui/lib/types/config.ts (3)
  • CoreConfig (336-350)
  • DefaultGlobalHeaderFilterConfig (310-313)
  • GlobalHeaderFilterConfig (304-307)
ui/app/_fallbacks/enterprise/lib/contexts/rbacContext.tsx (1)
  • useRbac (63-65)
ui/lib/store/apis/baseApi.ts (1)
  • getErrorMessage (174-199)
ui/components/ui/input.tsx (1)
  • Input (15-69)
framework/configstore/store.go (2)
framework/configstore/tables/config.go (1)
  • GlobalHeaderFilterConfig (46-49)
ui/lib/types/config.ts (1)
  • GlobalHeaderFilterConfig (304-307)
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
🔇 Additional comments (14)
ui/app/workspace/config/views/clientSettingsView.tsx (3)

101-164: LGTM!

The header filter list handlers are well-structured with proper memoization via useCallback. The lowercase normalization on input change aligns with the security requirements mentioned in the PR objectives.


166-221: LGTM!

The component layout and toggle sections are well-structured. RBAC checks are consistently applied to disable controls when the user lacks update permissions. The conditional display of dropped request count is a nice UX touch.


223-349: LGTM!

The header forwarding section provides excellent documentation through the Alert components, clearly explaining the allowlist/denylist behavior and the always-blocked security headers. The UI flows for managing headers are intuitive.

framework/configstore/clientconfig.go (2)

37-50: ClientConfig: header filter field wiring looks consistent

Adding HeaderFilterConfig *tables.GlobalHeaderFilterConfig with omitempty and a clear comment fits the existing config surface and keeps it optional without breaking JSON shape.


144-170: HeaderFilterConfig hashing is deterministic and aligned with semantics

Including sorted allowlist/denylist in GenerateClientConfigHash (with explicit prefixes) makes hash changes stable and order‑insensitive, matching how other slice fields are handled and what the filter logic expects.

transports/bifrost-http/handlers/inference.go (1)

450-451: Consistent propagation of header filter config into all inference/file handlers

Passing h.config.GetHeaderFilterConfig() into ConvertToBifrostContext for every handler keeps header forwarding rules centralized and guarantees that the allowlist/denylist is respected across all Bifrost HTTP endpoints without altering existing cancellation/error behavior.

Also applies to: 573-574, 649-650, 731-732, 803-805, 879-880, 1010-1011, 1096-1097, 1395-1397, 1448-1449, 1487-1488, 1526-1527, 1565-1566, 1647-1648, 1706-1707, 1745-1746, 1784-1785, 1823-1824

transports/bifrost-http/integrations/router.go (1)

393-400: Integration router now correctly applies global header filter config

Using g.handlerStore.GetHeaderFilterConfig() when building bifrostCtx aligns integrations with the core handlers so that x‑bf‑eh‑* forwarding is governed by the same allowlist/denylist rules on all routes.

transports/bifrost-http/handlers/mcp.go (1)

67-68: MCP execution correctly opts into header filtering

Passing h.store.GetHeaderFilterConfig() into ConvertToBifrostContext for MCP tool execution keeps header behavior consistent with the rest of the transport while still disallowing direct keys for this endpoint.

framework/configstore/store.go (1)

126-129: ConfigStore extension for header filter config is well-scoped

Adding dedicated GetHeaderFilterConfig / UpdateHeaderFilterConfig methods mirrors existing global config CRUD patterns (e.g., proxy, restart_required) and gives a clear persistence API for GlobalHeaderFilterConfig.

transports/bifrost-http/server/server.go (1)

53-67: Server-side reload hook for header filter config is straightforward and sufficient

Extending ServerCallbacks and implementing ReloadHeaderFilterConfig by updating s.Config.ClientConfig.HeaderFilterConfig and logging allowlist/denylist sizes gives a clear, low‑friction way to hot‑apply header filter changes without restart and fits the existing Reload*Config patterns in this server.

Also applies to: 841-856

transports/bifrost-http/lib/ctx.go (4)

11-11: LGTM!

Imports are appropriate for the new header filtering functionality.

Also applies to: 18-18


80-80: LGTM!

The function signature extension is clean. Using a pointer allows nil to represent "no filtering config" with graceful handling inside the function.


100-116: LGTM!

Good rename to securityDenylist to clearly distinguish the always-enforced security list from the user-configurable denylist. The coverage of security-sensitive headers (auth, proxy-auth, cookies, connection headers, and key-override headers) is comprehensive.


287-294: LGTM!

Correct enforcement order: security denylist is checked first (always enforced), then the configurable filter is applied. This ensures security-sensitive headers are never forwarded regardless of user configuration.

@akshaydeo akshaydeo force-pushed the 12-26-adds_allowlist_and_denylist_for_headers branch from a65444e to 312c22a Compare December 26, 2025 13:18
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
transports/bifrost-http/lib/ctx.go (1)

118-145: Header filter allow/deny checks are case-sensitive despite lowercasing header names.

labelName is normalized to lowercase before calling shouldAllowHeader, but the allow/deny checks use slices.Contains against the raw Allowlist/Denylist, which are not normalized. A config entry like "Content-Type" will not match the incoming "content-type", so filters behave unexpectedly and contradict the stated case‑insensitive semantics.

Recommend making membership checks case‑insensitive (or normalizing config to lowercase on load). One local fix is:

Proposed fix: use case-insensitive membership via strings.EqualFold
-		hasAllowlist := len(headerFilterConfig.Allowlist) > 0
-		hasDenylist := len(headerFilterConfig.Denylist) > 0
+		allowlist := headerFilterConfig.Allowlist
+		denylist := headerFilterConfig.Denylist
+		hasAllowlist := len(allowlist) > 0
+		hasDenylist := len(denylist) > 0
@@
-		if hasAllowlist {
-			if !slices.Contains(headerFilterConfig.Allowlist, headerName) {
+		if hasAllowlist {
+			if !slices.ContainsFunc(allowlist, func(s string) bool {
+				return strings.EqualFold(s, headerName)
+			}) {
 				return false
 			}
 		}
 		// If denylist is non-empty, header must not be in denylist
-		if hasDenylist {
-			if slices.Contains(headerFilterConfig.Denylist, headerName) {
+		if hasDenylist {
+			if slices.ContainsFunc(denylist, func(s string) bool {
+				return strings.EqualFold(s, headerName)
+			}) {
 				return false
 			}
 		}

This keeps header matching truly case-insensitive while leaving config storage format unchanged.

🧹 Nitpick comments (5)
ui/lib/types/schemas.ts (1)

680-691: Global header filter schemas/types line up with backend; optional normalization.

The Zod schemas and inferred types correctly mirror the backend GlobalHeaderFilterConfig (allowlist/denylist) and the header_filter_config wrapper shape, so they should interop cleanly with /api/config. If header names are meant to be case-insensitive, you may optionally add a .transform() (e.g., s.trim().toLowerCase()) on the string items here so UI input is canonicalized before it hits the backend filters.

Also applies to: 709-710

transports/bifrost-http/handlers/config.go (1)

34-35: Header filter config wiring is sound; verify persistence and equality semantics.

  • Extending ConfigManager with ReloadHeaderFilterConfig and calling it from updateConfig when HeaderFilterConfig changes is a good way to hot‑reload filter rules without a restart.
  • getConfig exposing header_filter_config via ConfigStore.GetHeaderFilterConfig gives the UI a clean, focused payload in addition to whatever is on client_config.
  • headerFilterConfigEqual’s pointer/len-based comparison (treating nil vs empty slices as equal and being order-sensitive) is reasonable; just be aware that reordering entries alone will trigger a reload.

The only thing that’s not obvious from this file is whether HeaderFilterConfig changes are persisted to the same storage that backs GetHeaderFilterConfig (e.g., via UpdateHeaderFilterConfig or inside UpdateClientConfig). If that linkage isn’t already in place in configstore, consider adding it so that:

  • GET /api/config always returns the latest header filter config, and
  • the configuration survives process restarts.

Also applies to: 161-167, 276-285, 633-642

transports/bifrost-http/lib/config.go (1)

36-41: Header filter config bootstrapping and HandlerStore exposure look correct.

  • Extending HandlerStore with GetHeaderFilterConfig and implementing it on Config as a lock-free accessor over ClientConfig.HeaderFilterConfig is consistent with the existing ShouldAllowDirectKeys pattern.
  • Both config load paths (file-backed and default/SQLite) now call loadDefaultHeaderFilterConfig, so runtime always picks up whatever ConfigStore.GetHeaderFilterConfig returns.
  • loadClientConfigFromFile syncing HeaderFilterConfig into the store and mergeClientConfig’s “DB wins, fill missing allow/deny lists from file” behavior give you a sane single source of truth while still honoring file defaults.

If you later decide to canonicalize header names (e.g., trimming and lowercasing allowlist/denylist entries once up front), loadDefaultHeaderFilterConfig and the sync block in loadClientConfigFromFile are good central places to do that so all consumers see normalized data.

Also applies to: 355-373, 501-506, 548-559, 1578-1580, 1763-1773, 2065-2071

ui/app/workspace/config/views/clientSettingsView.tsx (2)

280-300: Using array index as key can cause input focus and value issues when items are removed.

When items are added or removed from the middle of the list, React's reconciliation may not correctly associate inputs with their values, potentially causing the cursor to jump or values to appear in wrong inputs.

Consider generating a stable unique ID when adding items, or using a combination approach.

🔎 Proposed approach using stable IDs

One approach is to store objects with IDs instead of plain strings:

// In state, use objects with stable IDs:
interface HeaderEntry { id: string; value: string; }

// When adding:
const handleAddAllowlistHeader = useCallback(() => {
  setLocalConfig((prev) => ({
    ...prev,
    header_filter_config: {
      ...prev.header_filter_config,
      allowlist: [...(prev.header_filter_config?.allowlist || []), { id: crypto.randomUUID(), value: "" }],
    },
  }));
}, []);

Alternatively, if keeping plain strings, you could use a composite key, though this is less robust:

key={`${header}-${index}`}

316-336: Same key={index} concern applies to denylist.

As noted for the allowlist above, using index as key here has the same potential for input confusion when items are removed. The same mitigation approach would apply.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a65444e and 312c22a.

📒 Files selected for processing (16)
  • framework/configstore/clientconfig.go
  • framework/configstore/rdb.go
  • framework/configstore/store.go
  • framework/configstore/tables/config.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/integrations/bedrock_test.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/lib/ctx.go
  • transports/bifrost-http/server/server.go
  • transports/config.schema.json
  • ui/app/workspace/config/views/clientSettingsView.tsx
  • ui/lib/types/config.ts
  • ui/lib/types/schemas.ts
🚧 Files skipped from review as they are similar to previous changes (9)
  • transports/bifrost-http/integrations/bedrock_test.go
  • transports/config.schema.json
  • framework/configstore/store.go
  • framework/configstore/clientconfig.go
  • framework/configstore/tables/config.go
  • ui/lib/types/config.ts
  • transports/bifrost-http/handlers/inference.go
  • framework/configstore/rdb.go
  • transports/bifrost-http/integrations/router.go
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

always check the stack if there is one for the current PR. do not give localized reviews for the PR, always see all changes in the light of the whole stack of PRs (if there is a stack, if there is no stack you can continue to make localized suggestions/reviews)

Files:

  • transports/bifrost-http/lib/ctx.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/server/server.go
  • ui/app/workspace/config/views/clientSettingsView.tsx
  • transports/bifrost-http/handlers/config.go
  • ui/lib/types/schemas.ts
  • transports/bifrost-http/lib/config.go
🧠 Learnings (2)
📚 Learning: 2025-12-09T17:07:42.007Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/schemas/account.go:9-18
Timestamp: 2025-12-09T17:07:42.007Z
Learning: In core/schemas/account.go, the HuggingFaceKeyConfig field within the Key struct is currently unused and reserved for future Hugging Face inference endpoint deployments. Do not flag this field as missing from OpenAPI documentation or require its presence in the API spec until the feature is actively implemented and used. When the feature is added, update the OpenAPI docs accordingly; otherwise, treat this field as non-breaking and not part of the current API surface.

Applied to files:

  • transports/bifrost-http/lib/ctx.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/server/server.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/lib/config.go
📚 Learning: 2025-12-12T08:25:02.629Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1000
File: transports/bifrost-http/integrations/router.go:709-712
Timestamp: 2025-12-12T08:25:02.629Z
Learning: In transports/bifrost-http/**/*.go, update streaming response handling to align with OpenAI Responses API: use typed SSE events such as response.created, response.output_text.delta, response.done, etc., and do not rely on the legacy data: [DONE] termination marker. Note that data: [DONE] is only used by the older Chat Completions and Text Completions streaming APIs. Ensure parsers, writers, and tests distinguish SSE events from the [DONE] sentinel and handle each event type accordingly for correct stream termination and progress updates.

Applied to files:

  • transports/bifrost-http/lib/ctx.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/server/server.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/lib/config.go
🧬 Code graph analysis (4)
transports/bifrost-http/lib/ctx.go (2)
framework/configstore/tables/config.go (1)
  • GlobalHeaderFilterConfig (46-49)
ui/lib/types/config.ts (1)
  • GlobalHeaderFilterConfig (304-307)
transports/bifrost-http/handlers/mcp.go (1)
transports/bifrost-http/lib/ctx.go (1)
  • ConvertToBifrostContext (80-369)
ui/app/workspace/config/views/clientSettingsView.tsx (3)
ui/lib/types/config.ts (3)
  • DefaultGlobalHeaderFilterConfig (310-313)
  • GlobalHeaderFilterConfig (304-307)
  • CoreConfig (336-350)
ui/lib/store/apis/baseApi.ts (1)
  • getErrorMessage (174-199)
ui/components/ui/input.tsx (1)
  • Input (15-69)
transports/bifrost-http/handlers/config.go (4)
framework/configstore/tables/config.go (1)
  • GlobalHeaderFilterConfig (46-49)
ui/lib/types/config.ts (1)
  • GlobalHeaderFilterConfig (304-307)
framework/configstore/clientconfig.go (1)
  • ClientConfig (36-51)
transports/bifrost-http/handlers/utils.go (1)
  • SendError (35-44)
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
🔇 Additional comments (6)
transports/bifrost-http/handlers/mcp.go (1)

67-68: MCP execute path correctly participates in header filtering.

Passing h.store.GetHeaderFilterConfig() into ConvertToBifrostContext aligns MCP tool execution with the inference/router paths and ensures extra headers here respect the same filter rules.

transports/bifrost-http/server/server.go (1)

66-67: Server-side header filter reload is correct; confirm persistence path.

The new ReloadHeaderFilterConfig callback cleanly updates the in-memory ClientConfig.HeaderFilterConfig and logs a concise summary, which is enough for live header filtering to see changes.

Since this method is intentionally in-memory only, please confirm that the handler/configstore path (e.g., UpdateClientConfig and/or UpdateHeaderFilterConfig) also persists the same config so that:

  • GetHeaderFilterConfig used by GET /api/config returns the updated version, and
  • the filter configuration survives process restarts.

Also applies to: 841-856

ui/app/workspace/config/views/clientSettingsView.tsx (4)

30-42: Order-sensitive comparison may not detect reordered headers as "unchanged".

The headerFilterConfigEqual function compares arrays element-by-element by index. If users manually reorder headers externally or via future drag-and-drop functionality, identical sets in different orders would be flagged as changed.

For the current UI (which doesn't support reordering), this works correctly. If reordering is added later, consider using a Set-based comparison.


81-96: Good sanitization of empty entries before save.

The cleanup logic correctly filters out empty and whitespace-only strings from both allowlist and denylist before persisting. This prevents invalid entries from being saved.


223-270: Clear and helpful documentation for users.

The accordion sections provide good explanations of how the allowlist/denylist precedence works and which headers are always blocked for security. This helps users understand the filtering behavior without needing external documentation.


119-127: Lowercase normalization applied correctly on input change.

Both handleAllowlistChange and handleDenylistChange correctly normalize header names to lowercase, ensuring case-insensitive matching as specified in the PR objectives.

Also applies to: 149-157

@akshaydeo akshaydeo force-pushed the 12-26-adds_allowlist_and_denylist_for_headers branch from 312c22a to dcb3c25 Compare December 26, 2025 16:41
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
transports/bifrost-http/lib/ctx.go (1)

80-85: Header filter checks are still case‑sensitive against lowercased header names.

Incoming headers are normalized to lowercase (keyStr, labelName), but both shouldAllowHeader and the direct forwarding block compare against headerFilterConfig.Allowlist/Denylist using slices.Contains, which is case‑sensitive. The UI now lowercases entries, but configs set via JSON or direct API can still use mixed‑case names (e.g., Content-Type), which won’t match the lowercased header keys and will silently bypass the filter logic.

To make matching truly case‑insensitive and robust across all writers, consider switching these membership tests to something like slices.ContainsFunc(list, func(s string) bool { return strings.EqualFold(s, headerName) }) (and the analogous check in the direct forwarding branch), or factoring out a helper that does EqualFold for both allowlist and denylist.

Also applies to: 100-115, 124-144, 279-297, 298-321

🧹 Nitpick comments (3)
transports/bifrost-http/handlers/config.go (1)

632-642: Consider: slices.Equal is order-sensitive.

The comparison uses slices.Equal which is order-sensitive. If two configs have the same headers in different order (e.g., ["a", "b"] vs ["b", "a"]), they will be considered different, triggering unnecessary reloads.

If header order is semantically irrelevant, consider sorting before comparison or using a set-based comparison.

🔎 Optional: Order-insensitive comparison
+import "sort"
+
+// slicesEqualUnordered compares two string slices ignoring order
+func slicesEqualUnordered(a, b []string) bool {
+	if len(a) != len(b) {
+		return false
+	}
+	aCopy := make([]string, len(a))
+	bCopy := make([]string, len(b))
+	copy(aCopy, a)
+	copy(bCopy, b)
+	sort.Strings(aCopy)
+	sort.Strings(bCopy)
+	return slices.Equal(aCopy, bCopy)
+}

 func headerFilterConfigEqual(a, b *configstoreTables.GlobalHeaderFilterConfig) bool {
 	if a == nil && b == nil {
 		return true
 	}
 	if a == nil || b == nil {
 		return false
 	}
-	return slices.Equal(a.Allowlist, b.Allowlist) && slices.Equal(a.Denylist, b.Denylist)
+	return slicesEqualUnordered(a.Allowlist, b.Allowlist) && slicesEqualUnordered(a.Denylist, b.Denylist)
 }
transports/bifrost-http/lib/config.go (1)

37-41: Header filter plumbing looks coherent; consider normalizing config values once at load.

The HandlerStore extension and the end‑to‑end wiring (client config merge, store sync via UpdateHeaderFilterConfig, default‐path load, and GetHeaderFilterConfig accessor) are consistent and match the intended “store as source of truth, file as seed” behavior; semantics for nil vs empty allow/deny lists also align with the runtime logic in ctx.go.

As a small robustness improvement, you could normalize header filter entries (e.g., strings.TrimSpace + strings.ToLower) once when merging/loading into ClientConfig.HeaderFilterConfig, so non‑UI writers (config file / direct API) can safely rely on case‑insensitive matching without having to follow the UI’s lowercase convention; this would keep ctx.go simple while hardening behavior against stray whitespace/case in persisted configs.

Also applies to: 385-388, 501-506, 548-559, 1578-1580, 1763-1773, 2065-2071

ui/app/workspace/config/views/clientSettingsView.tsx (1)

27-42: UI wiring for header forwarding looks good; trim+lowercase values to avoid subtle mismatches.

The defaulting of header_filter_config, equality helper, and the allowlist/denylist UI all line up well with the backend, and forcing value.toLowerCase() ensures the lists stay consistent with lowercased header keys in ctx.go. One small robustness tweak: consider normalizing with value.trim().toLowerCase() in handleAllowlistChange/handleDenylistChange (and optionally serializing the trimmed version in handleSave) so that stray leading/trailing spaces don’t result in entries like " custom-id" that will never match any real header name.

Also applies to: 59-65, 81-91, 98-157, 295-371

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 312c22a and dcb3c25.

📒 Files selected for processing (16)
  • framework/configstore/clientconfig.go
  • framework/configstore/rdb.go
  • framework/configstore/store.go
  • framework/configstore/tables/config.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/integrations/bedrock_test.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/lib/ctx.go
  • transports/bifrost-http/server/server.go
  • transports/config.schema.json
  • ui/app/workspace/config/views/clientSettingsView.tsx
  • ui/lib/types/config.ts
  • ui/lib/types/schemas.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • transports/config.schema.json
  • framework/configstore/store.go
  • transports/bifrost-http/handlers/inference.go
  • framework/configstore/clientconfig.go
  • framework/configstore/tables/config.go
  • ui/lib/types/schemas.ts
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

always check the stack if there is one for the current PR. do not give localized reviews for the PR, always see all changes in the light of the whole stack of PRs (if there is a stack, if there is no stack you can continue to make localized suggestions/reviews)

Files:

  • transports/bifrost-http/integrations/bedrock_test.go
  • transports/bifrost-http/handlers/config.go
  • ui/lib/types/config.ts
  • transports/bifrost-http/lib/ctx.go
  • framework/configstore/rdb.go
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/server/server.go
  • transports/bifrost-http/handlers/mcp.go
  • ui/app/workspace/config/views/clientSettingsView.tsx
  • transports/bifrost-http/integrations/router.go
🧠 Learnings (2)
📚 Learning: 2025-12-09T17:07:42.007Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/schemas/account.go:9-18
Timestamp: 2025-12-09T17:07:42.007Z
Learning: In core/schemas/account.go, the HuggingFaceKeyConfig field within the Key struct is currently unused and reserved for future Hugging Face inference endpoint deployments. Do not flag this field as missing from OpenAPI documentation or require its presence in the API spec until the feature is actively implemented and used. When the feature is added, update the OpenAPI docs accordingly; otherwise, treat this field as non-breaking and not part of the current API surface.

Applied to files:

  • transports/bifrost-http/integrations/bedrock_test.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/lib/ctx.go
  • framework/configstore/rdb.go
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/server/server.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/integrations/router.go
📚 Learning: 2025-12-12T08:25:02.629Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1000
File: transports/bifrost-http/integrations/router.go:709-712
Timestamp: 2025-12-12T08:25:02.629Z
Learning: In transports/bifrost-http/**/*.go, update streaming response handling to align with OpenAI Responses API: use typed SSE events such as response.created, response.output_text.delta, response.done, etc., and do not rely on the legacy data: [DONE] termination marker. Note that data: [DONE] is only used by the older Chat Completions and Text Completions streaming APIs. Ensure parsers, writers, and tests distinguish SSE events from the [DONE] sentinel and handle each event type accordingly for correct stream termination and progress updates.

Applied to files:

  • transports/bifrost-http/integrations/bedrock_test.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/lib/ctx.go
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/server/server.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/integrations/router.go
🧬 Code graph analysis (10)
transports/bifrost-http/integrations/bedrock_test.go (2)
framework/configstore/tables/config.go (1)
  • GlobalHeaderFilterConfig (46-49)
ui/lib/types/config.ts (1)
  • GlobalHeaderFilterConfig (304-307)
transports/bifrost-http/handlers/config.go (4)
framework/configstore/tables/config.go (1)
  • GlobalHeaderFilterConfig (46-49)
ui/lib/types/config.ts (1)
  • GlobalHeaderFilterConfig (304-307)
framework/configstore/clientconfig.go (1)
  • ClientConfig (36-51)
transports/bifrost-http/handlers/utils.go (1)
  • SendError (35-44)
ui/lib/types/config.ts (1)
framework/configstore/tables/config.go (1)
  • GlobalHeaderFilterConfig (46-49)
transports/bifrost-http/lib/ctx.go (2)
framework/configstore/tables/config.go (1)
  • GlobalHeaderFilterConfig (46-49)
ui/lib/types/config.ts (1)
  • GlobalHeaderFilterConfig (304-307)
framework/configstore/rdb.go (2)
framework/configstore/tables/config.go (4)
  • GlobalHeaderFilterConfig (46-49)
  • TableGovernanceConfig (52-55)
  • TableGovernanceConfig (58-58)
  • ConfigHeaderFilterKey (12-12)
ui/lib/types/config.ts (1)
  • GlobalHeaderFilterConfig (304-307)
transports/bifrost-http/lib/config.go (3)
framework/configstore/tables/config.go (1)
  • GlobalHeaderFilterConfig (46-49)
ui/lib/types/config.ts (1)
  • GlobalHeaderFilterConfig (304-307)
framework/configstore/clientconfig.go (1)
  • ClientConfig (36-51)
transports/bifrost-http/server/server.go (4)
framework/configstore/tables/config.go (1)
  • GlobalHeaderFilterConfig (46-49)
ui/lib/types/config.ts (1)
  • GlobalHeaderFilterConfig (304-307)
transports/bifrost-http/lib/config.go (1)
  • Config (186-217)
framework/configstore/clientconfig.go (1)
  • ClientConfig (36-51)
transports/bifrost-http/handlers/mcp.go (1)
transports/bifrost-http/lib/ctx.go (1)
  • ConvertToBifrostContext (80-392)
ui/app/workspace/config/views/clientSettingsView.tsx (2)
ui/lib/types/config.ts (3)
  • DefaultGlobalHeaderFilterConfig (310-313)
  • GlobalHeaderFilterConfig (304-307)
  • CoreConfig (336-350)
framework/configstore/tables/config.go (1)
  • GlobalHeaderFilterConfig (46-49)
transports/bifrost-http/integrations/router.go (1)
transports/bifrost-http/lib/ctx.go (1)
  • ConvertToBifrostContext (80-392)
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
🔇 Additional comments (12)
transports/bifrost-http/handlers/mcp.go (1)

67-67: LGTM!

The header filter configuration is correctly passed to ConvertToBifrostContext, aligning with the updated signature and ensuring MCP tool execution respects header filtering rules.

transports/bifrost-http/integrations/bedrock_test.go (1)

15-30: LGTM!

The mock correctly implements GetHeaderFilterConfig() to satisfy the updated lib.HandlerStore interface. The compile-time interface assertion at line 30 ensures the mock stays synchronized with interface changes.

transports/bifrost-http/handlers/config.go (2)

161-167: LGTM!

Header filter config is correctly fetched from the store and included in the response when available. The nil check ensures no empty entry is added to the response map.


276-284: LGTM!

The change detection and reload flow is well-structured. The early return on reload failure ensures partial updates don't persist, maintaining configuration consistency.

ui/lib/types/config.ts (3)

302-313: LGTM!

The GlobalHeaderFilterConfig interface and default constant are correctly defined and align with the Go backend struct at framework/configstore/tables/config.go. The optional array types match the omitempty JSON tags in Go.


327-327: LGTM!

The header_filter_config field is correctly added to BifrostConfig, enabling the UI to receive and display the header filter configuration from the API response.


349-349: LGTM!

The header_filter_config field in CoreConfig matches the Go ClientConfig struct, ensuring type consistency for configuration updates.

framework/configstore/rdb.go (2)

1952-1969: LGTM!

The GetHeaderFilterConfig implementation follows the established pattern used by GetProxyConfig and other governance config retrieval methods. The nil handling for both missing records and empty values is appropriate.


1971-1981: LGTM!

The UpdateHeaderFilterConfig implementation is straightforward and consistent with UpdateProxyConfig. Using Save with the governance config key ensures proper upsert behavior.

transports/bifrost-http/server/server.go (2)

66-66: LGTM!

The ReloadHeaderFilterConfig method is correctly added to the ServerCallbacks interface, enabling the config handler to trigger runtime updates.


841-856: LGTM!

The implementation correctly stores the header filter config in ClientConfig.HeaderFilterConfig and includes defensive nil checks before accessing slice lengths. The logging provides useful visibility into the configuration state.

The pattern matches ReloadProxyConfig above, maintaining consistency across runtime configuration updates.

transports/bifrost-http/integrations/router.go (1)

383-399: Passing header filter config into ConvertToBifrostContext is consistent and minimal.

The updated call cleanly threads GetHeaderFilterConfig() into context creation without affecting the existing request/streaming pipeline, and lines up with the new HandlerStore contract in lib/config.go.

@akshaydeo akshaydeo force-pushed the 12-26-adds_allowlist_and_denylist_for_headers branch from dcb3c25 to d1b85e4 Compare December 29, 2025 03:33
@akshaydeo akshaydeo force-pushed the 12-26-minor_ux_uplifg_restart_required_status_storage branch 3 times, most recently from 4f454ea to 6bcbc30 Compare December 29, 2025 03:37
@akshaydeo akshaydeo force-pushed the 12-26-adds_allowlist_and_denylist_for_headers branch from d1b85e4 to 11fcf39 Compare December 29, 2025 03:37
@akshaydeo akshaydeo changed the base branch from 12-26-minor_ux_uplifg_restart_required_status_storage to graphite-base/1178 December 29, 2025 03:40
@akshaydeo akshaydeo force-pushed the 12-26-adds_allowlist_and_denylist_for_headers branch from 11fcf39 to 69645a0 Compare December 29, 2025 03:42
@graphite-app graphite-app bot changed the base branch from graphite-base/1178 to main December 29, 2025 03:43
@akshaydeo akshaydeo force-pushed the 12-26-adds_allowlist_and_denylist_for_headers branch from 69645a0 to 8a88ccf Compare December 29, 2025 03:43
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
transports/bifrost-http/handlers/config.go (1)

223-225: Header filter config reload is not clearly persisted and equality is order-sensitive

Two points to address:

  1. Persistence gap (likely bug)

    • getConfig reads the header filter via ConfigStore.GetHeaderFilterConfig(ctx) and exposes it as top-level header_filter_config.
    • updateConfig updates updatedConfig.HeaderFilterConfig and calls configManager.ReloadHeaderFilterConfig, but never calls a store-level UpdateHeaderFilterConfig.
    • Unless ReloadHeaderFilterConfig itself writes through to the config store, this means header filter changes coming from /api/config will not be persisted in the governance store, and may:
      • Not show up in subsequent /api/config?from_db=true reads, and
      • Be lost on server restart.

    Consider wiring persistence explicitly here, e.g.:

    if !headerFilterConfigEqual(payload.ClientConfig.HeaderFilterConfig, currentConfig.HeaderFilterConfig) {
        updatedConfig.HeaderFilterConfig = payload.ClientConfig.HeaderFilterConfig
    
        if err := h.store.ConfigStore.UpdateHeaderFilterConfig(ctx, payload.ClientConfig.HeaderFilterConfig); err != nil {
            logger.Warn(fmt.Sprintf("failed to save header filter config: %v", err))
            SendError(ctx, fasthttp.StatusInternalServerError, fmt.Sprintf("failed to save header filter config: %v", err))
            return
        }
    
        if err := h.configManager.ReloadHeaderFilterConfig(ctx, payload.ClientConfig.HeaderFilterConfig); err != nil {
            logger.Warn(fmt.Sprintf("failed to reload header filter config: %v", err))
            SendError(ctx, fasthttp.StatusInternalServerError, fmt.Sprintf("failed to reload header filter config: %v", err))
            return
        }
    }

    Or, if persistence is intentionally delegated to ReloadHeaderFilterConfig, document that and ensure all implementations write through consistently.

  2. Order-sensitive equality

    • headerFilterConfigEqual uses slices.Equal on Allowlist and Denylist, so [ "a", "b" ] differs from [ "b", "a" ]. That means harmless reordering in the UI will trigger reloads and DB writes.
    • If you intend allow/deny lists to behave as sets, sort copies before comparing or compare via map/set.

    For example:

    func headerFilterConfigEqual(a, b *configstoreTables.GlobalHeaderFilterConfig) bool {
        if a == nil && b == nil {
            return true
        }
        if a == nil || b == nil {
            return false
        }
        al := slices.Clone(a.Allowlist)
        bl := slices.Clone(b.Allowlist)
        ad := slices.Clone(a.Denylist)
        bd := slices.Clone(b.Denylist)
        slices.Sort(al); slices.Sort(bl)
        slices.Sort(ad); slices.Sort(bd)
        return slices.Equal(al, bl) && slices.Equal(ad, bd)
    }

Also applies to: 276-285, 295-301, 633-642

♻️ Duplicate comments (1)
transports/bifrost-http/lib/ctx.go (1)

9-23: Header filter allow/deny checks are still case-sensitive against lowercased headers

The overall structure here (security denylist + configurable allow/deny via headerFilterConfig + optional direct forwarding when an allowlist exists) looks good, but the earlier case-sensitivity problem still applies:

  • Incoming header names (keyStr, and labelName for x-bf-eh-*) are normalized to lowercase.
  • shouldAllowHeader and the direct-forwarding block both use slices.Contains(headerFilterConfig.Allowlist, headerName) and slices.Contains(headerFilterConfig.Denylist, headerName) with no normalization of the configured lists.
  • If the config contains "Content-Type" while the incoming header is seen as "content-type", it won’t match, so the filter will misbehave (either unintentionally blocking or allowing headers).

To make matching truly case-insensitive and predictable you could either:

  1. Normalize config on load (preferred for perf):

    • When reading or updating GlobalHeaderFilterConfig from config/DB, lowercase all entries in Allowlist and Denylist.
    • Then keep using slices.Contains on the already-lowercased lists here.
  2. Do case-insensitive membership checks in this function:

    • Replace slices.Contains with slices.ContainsFunc(..., func(s string) bool { return strings.EqualFold(s, headerName) }) in both shouldAllowHeader and the direct-forwarding block.

Also, consider reusing shouldAllowHeader(keyStr) in the direct-forwarding path instead of reimplementing allow/deny logic there to keep semantics in one place.

Also applies to: 80-85, 100-145, 279-297, 298-321

🧹 Nitpick comments (2)
transports/bifrost-http/lib/config.go (1)

1763-1773: Consider adding a nil check for defensive programming.

While both current call sites ensure ConfigStore is non-nil before calling this function, adding a guard clause inside the function would make it safer if called from new locations in the future.

🔎 Proposed fix
 func loadDefaultHeaderFilterConfig(ctx context.Context, config *Config) {
+	if config.ConfigStore == nil {
+		return
+	}
 	headerFilterConfig, err := config.ConfigStore.GetHeaderFilterConfig(ctx)
 	if err != nil {
 		logger.Warn("failed to get header filter config from store: %v", err)
 		return
 	}
 	if headerFilterConfig != nil {
 		config.ClientConfig.HeaderFilterConfig = headerFilterConfig
 	}
 }
ui/app/workspace/config/views/clientSettingsView.tsx (1)

305-331: Consider using stable keys for list items.

Using index as the React key works here since items aren't reordered, but if you add reordering functionality later, you'd need to update this. A simple improvement would be to use the header value itself as the key (since headers should be unique):

-{(localConfig.header_filter_config?.allowlist || []).map((header, index) => (
-	<div key={index} className="flex items-center gap-2">
+{(localConfig.header_filter_config?.allowlist || []).map((header, index) => (
+	<div key={`allowlist-${index}-${header}`} className="flex items-center gap-2">

This is a minor improvement and not blocking.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dcb3c25 and 8a88ccf.

📒 Files selected for processing (16)
  • framework/configstore/clientconfig.go
  • framework/configstore/rdb.go
  • framework/configstore/store.go
  • framework/configstore/tables/config.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/integrations/bedrock_test.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/lib/ctx.go
  • transports/bifrost-http/server/server.go
  • transports/config.schema.json
  • ui/app/workspace/config/views/clientSettingsView.tsx
  • ui/lib/types/config.ts
  • ui/lib/types/schemas.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • transports/bifrost-http/integrations/bedrock_test.go
  • ui/lib/types/schemas.ts
  • framework/configstore/store.go
  • transports/bifrost-http/server/server.go
  • transports/config.schema.json
  • framework/configstore/rdb.go
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

always check the stack if there is one for the current PR. do not give localized reviews for the PR, always see all changes in the light of the whole stack of PRs (if there is a stack, if there is no stack you can continue to make localized suggestions/reviews)

Files:

  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/handlers/mcp.go
  • ui/lib/types/config.ts
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/lib/ctx.go
  • ui/app/workspace/config/views/clientSettingsView.tsx
  • transports/bifrost-http/lib/config.go
  • framework/configstore/tables/config.go
  • framework/configstore/clientconfig.go
🧠 Learnings (2)
📚 Learning: 2025-12-09T17:07:42.007Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/schemas/account.go:9-18
Timestamp: 2025-12-09T17:07:42.007Z
Learning: In core/schemas/account.go, the HuggingFaceKeyConfig field within the Key struct is currently unused and reserved for future Hugging Face inference endpoint deployments. Do not flag this field as missing from OpenAPI documentation or require its presence in the API spec until the feature is actively implemented and used. When the feature is added, update the OpenAPI docs accordingly; otherwise, treat this field as non-breaking and not part of the current API surface.

Applied to files:

  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/lib/ctx.go
  • transports/bifrost-http/lib/config.go
  • framework/configstore/tables/config.go
  • framework/configstore/clientconfig.go
📚 Learning: 2025-12-12T08:25:02.629Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1000
File: transports/bifrost-http/integrations/router.go:709-712
Timestamp: 2025-12-12T08:25:02.629Z
Learning: In transports/bifrost-http/**/*.go, update streaming response handling to align with OpenAI Responses API: use typed SSE events such as response.created, response.output_text.delta, response.done, etc., and do not rely on the legacy data: [DONE] termination marker. Note that data: [DONE] is only used by the older Chat Completions and Text Completions streaming APIs. Ensure parsers, writers, and tests distinguish SSE events from the [DONE] sentinel and handle each event type accordingly for correct stream termination and progress updates.

Applied to files:

  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/lib/ctx.go
  • transports/bifrost-http/lib/config.go
🧬 Code graph analysis (9)
transports/bifrost-http/handlers/inference.go (1)
transports/bifrost-http/lib/ctx.go (1)
  • ConvertToBifrostContext (80-392)
transports/bifrost-http/handlers/mcp.go (1)
transports/bifrost-http/lib/ctx.go (1)
  • ConvertToBifrostContext (80-392)
ui/lib/types/config.ts (1)
framework/configstore/tables/config.go (1)
  • GlobalHeaderFilterConfig (46-49)
transports/bifrost-http/integrations/router.go (1)
transports/bifrost-http/lib/ctx.go (1)
  • ConvertToBifrostContext (80-392)
transports/bifrost-http/lib/ctx.go (2)
framework/configstore/tables/config.go (1)
  • GlobalHeaderFilterConfig (46-49)
ui/lib/types/config.ts (1)
  • GlobalHeaderFilterConfig (304-307)
ui/app/workspace/config/views/clientSettingsView.tsx (2)
ui/lib/types/config.ts (2)
  • DefaultGlobalHeaderFilterConfig (310-313)
  • GlobalHeaderFilterConfig (304-307)
framework/configstore/tables/config.go (1)
  • GlobalHeaderFilterConfig (46-49)
transports/bifrost-http/lib/config.go (4)
framework/configstore/tables/config.go (1)
  • GlobalHeaderFilterConfig (46-49)
ui/lib/types/config.ts (1)
  • GlobalHeaderFilterConfig (304-307)
framework/configstore/store.go (1)
  • ConfigStore (17-161)
framework/configstore/clientconfig.go (1)
  • ClientConfig (36-51)
framework/configstore/tables/config.go (2)
ui/lib/types/config.ts (1)
  • GlobalProxyType (269-269)
core/network/http.go (1)
  • GlobalProxyType (46-46)
framework/configstore/clientconfig.go (2)
framework/configstore/tables/config.go (1)
  • GlobalHeaderFilterConfig (46-49)
ui/lib/types/config.ts (1)
  • GlobalHeaderFilterConfig (304-307)
🪛 GitHub Actions: PR #1178
transports/bifrost-http/lib/config.go

[error] 40-45: undefined: configstoreTables.GlobalHeaderFilterConfig

🔇 Additional comments (16)
framework/configstore/clientconfig.go (1)

37-51: ClientConfig header filter integration and hashing look solid

The new HeaderFilterConfig field and its inclusion in GenerateClientConfigHash (with sorted allowlist/denylist and explicit prefixes) are deterministic and consistent with existing hashing patterns. No issues from this side.

Also applies to: 144-171

transports/bifrost-http/handlers/mcp.go (1)

66-68: MCP handler wiring of header filter config is correct

Passing h.store.GetHeaderFilterConfig() into ConvertToBifrostContext while keeping allowDirectKeys false is consistent with the new API and expected behavior.

transports/bifrost-http/handlers/inference.go (1)

449-451: Consistent propagation of header filter config across inference handlers

All inference, batch, and file endpoints now pass both ShouldAllowDirectKeys() and GetHeaderFilterConfig() into ConvertToBifrostContext, so header filtering applies uniformly without changing existing cancellation/streaming semantics. Looks good.

Also applies to: 573-574, 649-650, 731-732, 803-805, 879-880, 1009-1011, 1096-1097, 1395-1396, 1448-1449, 1487-1488, 1526-1527, 1565-1566, 1647-1648, 1706-1707, 1745-1746, 1784-1785, 1823-1824

framework/configstore/tables/config.go (1)

5-13: GlobalHeaderFilterConfig schema is aligned and clear

The new ConfigHeaderFilterKey and GlobalHeaderFilterConfig{Allowlist,Denylist} match the documented filtering rules and the TS types. No backend schema issues spotted.

Also applies to: 40-49

transports/bifrost-http/handlers/config.go (1)

25-35: Config manager/API exposure for header filter config looks consistent

Adding ReloadHeaderFilterConfig to ConfigManager and surfacing header_filter_config from the store in getConfig mirrors how proxy and auth config are handled and keeps the API shape coherent. Just ensure all ConfigManager implementations are updated to satisfy the new method.

Also applies to: 161-167

ui/lib/types/config.ts (1)

268-314: TS types for header_filter_config correctly mirror Go structs

GlobalHeaderFilterConfig and its default value match tables.GlobalHeaderFilterConfig, and wiring it into both BifrostConfig and CoreConfig keeps the UI aligned with the backend schema. No issues from a typing perspective.

Also applies to: 321-350

transports/bifrost-http/integrations/router.go (1)

383-404: Generic router now correctly passes header filter config into context

The updated ConvertToBifrostContext call uses both ShouldAllowDirectKeys() and GetHeaderFilterConfig(), so integrations benefit from the same header filtering behavior as the core handlers without altering existing flow control.

transports/bifrost-http/lib/config.go (4)

548-559: LGTM!

The merge logic for HeaderFilterConfig correctly follows the established pattern: DB values take priority, and empty values are filled from the config file. The handling of both struct-level and list-level merging is well-implemented.


2065-2071: LGTM!

The lock-free read pattern is consistent with ShouldAllowDirectKeys() and the rationale is well-documented. Pointer reads in Go are atomic, making this safe for the intended use case.


500-506: LGTM!

The sync logic correctly guards against nil HeaderFilterConfig and nil ConfigStore, and uses an appropriate warning log level for best-effort synchronization failures.


385-389: LGTM!

The placement after governance loading and the ConfigStore nil check are appropriate. The comment clarifies that header filter config is runtime-only, which helps explain why it's loaded from the store rather than the config file.

ui/app/workspace/config/views/clientSettingsView.tsx (5)

30-42: Order-sensitive comparison may cause unexpected behavior.

The headerFilterConfigEqual function compares arrays by index, meaning ["a", "b"] and ["b", "a"] are considered different. This could cause hasChanges to return true even when the headers are semantically the same but in different order (e.g., if the API returns them in a different order than the user entered).

Consider whether order matters for header filtering semantics. If not, you might want a set-based comparison.

🔎 Proposed fix (if order doesn't matter)
 function headerFilterConfigEqual(a?: GlobalHeaderFilterConfig, b?: GlobalHeaderFilterConfig): boolean {
 	const aAllowlist = a?.allowlist || [];
 	const bAllowlist = b?.allowlist || [];
 	const aDenylist = a?.denylist || [];
 	const bDenylist = b?.denylist || [];

 	if (aAllowlist.length !== bAllowlist.length || aDenylist.length !== bDenylist.length) {
 		return false;
 	}

-	return aAllowlist.every((v, i) => v === bAllowlist[i]) && aDenylist.every((v, i) => v === bDenylist[i]);
+	const aAllowSet = new Set(aAllowlist);
+	const bAllowSet = new Set(bAllowlist);
+	const aDenySet = new Set(aDenylist);
+	const bDenySet = new Set(bDenylist);
+
+	const setsEqual = (a: Set<string>, b: Set<string>) => 
+		a.size === b.size && [...a].every((v) => b.has(v));
+
+	return setsEqual(aAllowSet, bAllowSet) && setsEqual(aDenySet, bDenySet);
 }

81-96: LGTM!

The cleanup logic correctly filters out empty and whitespace-only entries before saving. The non-null assertion on bifrostConfig is safe here since the save button is only enabled when hasChanges is true, which requires config to be defined.


98-157: LGTM!

The list handlers are well-implemented with:

  • Proper useCallback usage with empty dependency arrays
  • Automatic lowercase conversion for case-insensitive header matching
  • Clean immutable state updates

223-293: LGTM!

Excellent inline documentation for users. The accordion sections clearly explain:

  • How the x-bf-eh-* prefix works
  • The relationship between allowlist/denylist and direct header forwarding
  • Security-blocked headers that cannot be overridden

This will greatly improve the user experience for configuring header forwarding.


3-28: LGTM!

The imports are appropriate for the new header filtering functionality, and the defaultConfig correctly includes DefaultGlobalHeaderFilterConfig as a fallback.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
transports/bifrost-http/lib/ctx.go (1)

80-145: Header filter wiring looks good overall; watch doc drift and case‑sensitivity of config lists

  • The new securityDenylist and shouldAllowHeader logic cleanly enforce “always-blocked” headers and the allowlist/denylist precedence, and the direct‑forwarding branch for allowlisted headers is consistent with the UI copy.
  • The example in the function doc still shows ConvertToBifrostContext(fastCtx, true) (two args) even though the signature now takes headerFilterConfig as a third parameter; it’d be good to update that snippet to avoid confusing integrators.
  • Header names from requests are normalized to lowercase (keyStr, labelName), but membership checks use slices.Contains on the raw allowlist/denylist values. The UI ensures lowercasing, but configs written directly via the API (or older data) can carry mixed‑case header names that will no longer match. Consider either normalizing the config to lowercase once when loading/saving or switching these checks to a case‑insensitive comparison so behavior doesn’t depend on how the config was authored. (This is the same underlying concern noted in the earlier review comment on this file.)

Also applies to: 279-321

transports/bifrost-http/lib/config.go (1)

454-507: Centralize and normalize header filter config when loading/syncing

The header filter config flow is coherent: ClientConfig.HeaderFilterConfig is merged from file/DB, synced to the store via UpdateHeaderFilterConfig, and reloaded via loadDefaultHeaderFilterConfig; handlers then read it via GetHeaderFilterConfig(). One improvement that also resolves the case-sensitivity concern in ctx.go:

  • Normalize HeaderFilterConfig.Allowlist and .Denylist to lowercase (and optionally trim/deduplicate) once when loading/syncing here, so downstream lookups against lowercased header names don’t depend on the casing used by the caller (UI vs direct API).

For example:

// helper near loadDefaultHeaderFilterConfig
func normalizeHeaderFilterConfig(cfg *configstoreTables.GlobalHeaderFilterConfig) {
	if cfg == nil {
		return
	}
	norm := func(in []string) []string {
		out := make([]string, 0, len(in))
		seen := make(map[string]struct{}, len(in))
		for _, h := range in {
			h = strings.ToLower(strings.TrimSpace(h))
			if h == "" {
				continue
			}
			if _, ok := seen[h]; ok {
				continue
			}
			seen[h] = struct{}{}
			out = append(out, h)
		}
		return out
	}
	cfg.Allowlist = norm(cfg.Allowlist)
	cfg.Denylist = norm(cfg.Denylist)
}

Then call normalizeHeaderFilterConfig in loadClientConfigFromFile before UpdateHeaderFilterConfig, in loadDefaultHeaderFilterConfig after GetHeaderFilterConfig, and anywhere else you hydrate ClientConfig.HeaderFilterConfig. That keeps the runtime config consistent regardless of how it was authored and makes the existing slices.Contains checks in ctx.go robust. (This aligns with the earlier review note about header filter case-sensitivity.)

Also applies to: 548-559, 1561-1580, 1763-1773, 2065-2071

🧹 Nitpick comments (2)
transports/bifrost-http/handlers/inference.go (1)

450-451: Core inference handlers now consistently use headerFilterConfig

All these ConvertToBifrostContext call sites (models, text, chat, responses, embeddings, speech, transcription, count_tokens) now pass h.config.GetHeaderFilterConfig() alongside ShouldAllowDirectKeys(), so they uniformly honor the global allowlist/denylist while remaining backward compatible when the config is nil. Minor nit: you could also route this via handlerStore.GetHeaderFilterConfig() for symmetry with ShouldAllowDirectKeys(), but the current approach is functionally fine.

Also applies to: 573-574, 649-650, 731-732, 803-805, 879-880, 1010-1011, 1096-1097

ui/app/workspace/config/views/clientSettingsView.tsx (1)

3-43: Header forwarding UI and state handling are coherent and match backend semantics

The new header_filter_config wiring (defaulting, change detection, trimming empty entries on save, and lowercasing inputs) looks solid and lines up with the server’s expectation of lowercase header names without the x-bf-eh- prefix. One minor nuance: headerFilterConfigEqual treats the allowlist/denylist as ordered sequences, so reordering entries without changing their content will still count as a change; that’s fine if order isn’t surfaced elsewhere, but you could optionally normalize/sort before comparing if you want purely set‑based semantics.

Also applies to: 61-65, 68-81, 98-158, 159-373

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dcb3c25 and 8a88ccf.

📒 Files selected for processing (16)
  • framework/configstore/clientconfig.go
  • framework/configstore/rdb.go
  • framework/configstore/store.go
  • framework/configstore/tables/config.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/integrations/bedrock_test.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/lib/ctx.go
  • transports/bifrost-http/server/server.go
  • transports/config.schema.json
  • ui/app/workspace/config/views/clientSettingsView.tsx
  • ui/lib/types/config.ts
  • ui/lib/types/schemas.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • transports/bifrost-http/handlers/config.go
  • ui/lib/types/schemas.ts
  • framework/configstore/tables/config.go
  • ui/lib/types/config.ts
  • transports/config.schema.json
  • transports/bifrost-http/integrations/bedrock_test.go
  • framework/configstore/clientconfig.go
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

always check the stack if there is one for the current PR. do not give localized reviews for the PR, always see all changes in the light of the whole stack of PRs (if there is a stack, if there is no stack you can continue to make localized suggestions/reviews)

Files:

  • transports/bifrost-http/lib/ctx.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/integrations/router.go
  • ui/app/workspace/config/views/clientSettingsView.tsx
  • transports/bifrost-http/server/server.go
  • transports/bifrost-http/handlers/mcp.go
  • framework/configstore/rdb.go
  • transports/bifrost-http/lib/config.go
  • framework/configstore/store.go
🧠 Learnings (2)
📚 Learning: 2025-12-09T17:07:42.007Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/schemas/account.go:9-18
Timestamp: 2025-12-09T17:07:42.007Z
Learning: In core/schemas/account.go, the HuggingFaceKeyConfig field within the Key struct is currently unused and reserved for future Hugging Face inference endpoint deployments. Do not flag this field as missing from OpenAPI documentation or require its presence in the API spec until the feature is actively implemented and used. When the feature is added, update the OpenAPI docs accordingly; otherwise, treat this field as non-breaking and not part of the current API surface.

Applied to files:

  • transports/bifrost-http/lib/ctx.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/server/server.go
  • transports/bifrost-http/handlers/mcp.go
  • framework/configstore/rdb.go
  • transports/bifrost-http/lib/config.go
  • framework/configstore/store.go
📚 Learning: 2025-12-12T08:25:02.629Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1000
File: transports/bifrost-http/integrations/router.go:709-712
Timestamp: 2025-12-12T08:25:02.629Z
Learning: In transports/bifrost-http/**/*.go, update streaming response handling to align with OpenAI Responses API: use typed SSE events such as response.created, response.output_text.delta, response.done, etc., and do not rely on the legacy data: [DONE] termination marker. Note that data: [DONE] is only used by the older Chat Completions and Text Completions streaming APIs. Ensure parsers, writers, and tests distinguish SSE events from the [DONE] sentinel and handle each event type accordingly for correct stream termination and progress updates.

Applied to files:

  • transports/bifrost-http/lib/ctx.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/server/server.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/lib/config.go
🧬 Code graph analysis (8)
transports/bifrost-http/handlers/inference.go (1)
transports/bifrost-http/lib/ctx.go (1)
  • ConvertToBifrostContext (80-392)
transports/bifrost-http/integrations/router.go (1)
transports/bifrost-http/lib/ctx.go (1)
  • ConvertToBifrostContext (80-392)
ui/app/workspace/config/views/clientSettingsView.tsx (2)
ui/lib/types/config.ts (2)
  • DefaultGlobalHeaderFilterConfig (310-313)
  • GlobalHeaderFilterConfig (304-307)
framework/configstore/tables/config.go (1)
  • GlobalHeaderFilterConfig (46-49)
transports/bifrost-http/server/server.go (4)
framework/configstore/tables/config.go (1)
  • GlobalHeaderFilterConfig (46-49)
ui/lib/types/config.ts (1)
  • GlobalHeaderFilterConfig (304-307)
transports/bifrost-http/lib/config.go (1)
  • Config (186-217)
framework/configstore/clientconfig.go (1)
  • ClientConfig (36-51)
transports/bifrost-http/handlers/mcp.go (1)
transports/bifrost-http/lib/ctx.go (1)
  • ConvertToBifrostContext (80-392)
framework/configstore/rdb.go (2)
framework/configstore/tables/config.go (4)
  • GlobalHeaderFilterConfig (46-49)
  • TableGovernanceConfig (52-55)
  • TableGovernanceConfig (58-58)
  • ConfigHeaderFilterKey (12-12)
ui/lib/types/config.ts (1)
  • GlobalHeaderFilterConfig (304-307)
transports/bifrost-http/lib/config.go (3)
framework/configstore/tables/config.go (1)
  • GlobalHeaderFilterConfig (46-49)
ui/lib/types/config.ts (1)
  • GlobalHeaderFilterConfig (304-307)
framework/configstore/clientconfig.go (1)
  • ClientConfig (36-51)
framework/configstore/store.go (2)
framework/configstore/tables/config.go (1)
  • GlobalHeaderFilterConfig (46-49)
ui/lib/types/config.ts (1)
  • GlobalHeaderFilterConfig (304-307)
🪛 GitHub Actions: PR #1178
transports/bifrost-http/lib/config.go

[error] 40-45: Undefined: configstoreTables.GlobalHeaderFilterConfig

🔇 Additional comments (7)
transports/bifrost-http/handlers/mcp.go (1)

66-68: Propagating headerFilterConfig into MCP context looks correct

Passing h.store.GetHeaderFilterConfig() into ConvertToBifrostContext cleanly aligns MCP tool execution with the new global header filtering behavior; no behavioral regressions are apparent.

transports/bifrost-http/handlers/inference.go (1)

1395-1396: Batch and file endpoints correctly wired into global header filter config

Batch (create/list/retrieve/cancel/results) and file (upload/list/retrieve/delete/content) handlers now also pass GetHeaderFilterConfig() into ConvertToBifrostContext, ensuring header forwarding rules are applied consistently across all inference-related APIs.

Also applies to: 1448-1449, 1487-1488, 1526-1527, 1565-1566, 1647-1648, 1706-1707, 1745-1746, 1784-1785, 1823-1824

framework/configstore/store.go (1)

126-129: ConfigStore interface extension for header filter config is consistent

Adding GetHeaderFilterConfig / UpdateHeaderFilterConfig under a dedicated header-filter CRUD section matches existing interface conventions and cleanly exposes GlobalHeaderFilterConfig; assuming all concrete stores implement these, this looks good.

transports/bifrost-http/integrations/router.go (1)

392-394: Integration router now passes headerFilterConfig into context conversion

createHandler correctly adds g.handlerStore.GetHeaderFilterConfig() to the ConvertToBifrostContext call so provider-specific integrations respect the same global header filtering as the native inference endpoints.

transports/bifrost-http/server/server.go (1)

66-67: Header filter reload callback is correctly plumbed and stateful

Adding ReloadHeaderFilterConfig to ServerCallbacks and implementing it by updating s.Config.ClientConfig.HeaderFilterConfig (with a concise allowlist/denylist length log) cleanly wires the persisted GlobalHeaderFilterConfig into the in-memory config used by HTTP handlers; nil configs are handled safely.

Also applies to: 841-856

framework/configstore/rdb.go (1)

1952-1981: Header filter config persistence matches existing governance-config patterns

The get/set helpers are consistent with GetProxyConfig/UpdateProxyConfig: they upsert a single governance_config row by key, treat missing/empty values as “no config”, and JSON‑roundtrip GlobalHeaderFilterConfig. No issues from a correctness or safety standpoint; normalization of header names (if desired) can be layered in higher-level config code rather than here.

transports/bifrost-http/lib/config.go (1)

33-41: The code is correct. GlobalHeaderFilterConfig is properly defined and exported in framework/configstore/tables/config.go (lines 40–49), and the import path and usage in config.go are accurate. This type is used consistently throughout the codebase with no compilation errors.

@akshaydeo akshaydeo force-pushed the 12-26-adds_allowlist_and_denylist_for_headers branch from 8a88ccf to fb4e862 Compare December 29, 2025 06:46
@akshaydeo akshaydeo force-pushed the 12-26-adds_allowlist_and_denylist_for_headers branch from fb4e862 to e593d42 Compare December 29, 2025 07:03
Copy link
Contributor Author

akshaydeo commented Dec 29, 2025

Merge activity

  • Dec 29, 7:04 AM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Dec 29, 7:04 AM UTC: @akshaydeo merged this pull request with Graphite.

@akshaydeo akshaydeo merged commit f104cf9 into main Dec 29, 2025
7 of 8 checks passed
@akshaydeo akshaydeo deleted the 12-26-adds_allowlist_and_denylist_for_headers branch December 29, 2025 07:04
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (5)
transports/bifrost-http/handlers/inference.go (1)

450-451: Header filter config is threaded consistently into all inference contexts.

All inference and batch/file handlers now pass h.config.GetHeaderFilterConfig() into ConvertToBifrostContext alongside ShouldAllowDirectKeys(), which keeps header‑filter semantics uniform across endpoints without changing existing control flow. If this pattern grows further, consider a small helper on CompletionHandler to DRY the repeated call, but it’s not required for correctness.

Also applies to: 573-574, 649-650, 731-732, 803-804, 879-880, 1010-1011, 1096-1097, 1395-1396, 1448-1449, 1487-1488, 1526-1527, 1565-1566, 1647-1648, 1706-1707, 1745-1746, 1784-1785, 1823-1824

transports/bifrost-http/handlers/config.go (1)

25-39: Header filter validation is sound; consider centralizing the security header list.

The new plumbing in ConfigHandler does the right things:

  • Rejects changes when HeaderFilterConfig actually differs, using headerFilterConfigEqual.
  • Validates that neither allowlist nor denylist contain any of the security‑sensitive headers defined in securityHeaders, case‑insensitively.
  • Applies the update both to updatedConfig and via ReloadHeaderFilterConfig, so runtime behavior changes immediately while the config store is still updated via UpdateClientConfig and ReloadClientConfigFromConfigStore.

This pairs well with the hardcoded security denylist in lib/ctx.go and closes the gap where a misconfigured allowlist might otherwise surprise operators.

One maintainability concern: securityHeaders here duplicates the denylist in lib/ctx.go. To avoid future drift, it would be cleaner to centralize this list (for example in a shared internal package or exported helper) and have both the handler and context logic reference the same source.

Also applies to: 50-51, 285-299, 648-689

transports/bifrost-http/lib/ctx.go (1)

125-149: LGTM! Case-insensitive header matching properly implemented.

The shouldAllowHeader closure correctly implements the filter precedence (allowlist then denylist) with case-insensitive comparison using strings.EqualFold. This addresses the case-sensitivity concern from the previous review.

Minor nit: extra blank line at line 145 before the closing brace.

🔎 Optional: Remove extra blank line
 		if slices.ContainsFunc(headerFilterConfig.Denylist, func(s string) bool {
 			return strings.EqualFold(s, headerName)
 		}) {
 			return false
-				
 		}
ui/app/workspace/config/views/clientSettingsView.tsx (2)

53-65: Consider order-insensitive comparison for allowlist/denylist.

The current headerFilterConfigEqual uses index-based comparison, meaning ["a", "b"]["b", "a"]. While this works for change detection (enabling save), it may cause hasChanges to be true even when the effective configuration is identical but in different order.

This is a minor UX consideration since header filter semantics are order-independent.

🔎 Optional: Order-insensitive comparison
function headerFilterConfigEqual(a?: GlobalHeaderFilterConfig, b?: GlobalHeaderFilterConfig): boolean {
	const aAllowlist = new Set(a?.allowlist || []);
	const bAllowlist = new Set(b?.allowlist || []);
	const aDenylist = new Set(a?.denylist || []);
	const bDenylist = new Set(b?.denylist || []);

	if (aAllowlist.size !== bAllowlist.size || aDenylist.size !== bDenylist.size) {
		return false;
	}

	return [...aAllowlist].every((v) => bAllowlist.has(v)) && 
	       [...aDenylist].every((v) => bDenylist.has(v));
}

361-384: Consider using stable keys instead of array index.

Using index as the React key can cause subtle issues when items are removed (e.g., focus jumping to wrong input). Since header names should be unique within each list, consider using the header value as the key with a fallback:

🔎 Optional: Use stable keys
-{(localConfig.header_filter_config?.allowlist || []).map((header, index) => (
-  <div key={index} className="flex items-center gap-2">
+{(localConfig.header_filter_config?.allowlist || []).map((header, index) => (
+  <div key={header || `empty-${index}`} className="flex items-center gap-2">

Apply similarly for the denylist section.

Also applies to: 403-426

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8a88ccf and fb4e862.

📒 Files selected for processing (17)
  • framework/configstore/clientconfig.go
  • framework/configstore/migrations.go
  • framework/configstore/rdb.go
  • framework/configstore/tables/clientconfig.go
  • framework/configstore/tables/config.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/integrations/bedrock_test.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/lib/ctx.go
  • transports/bifrost-http/server/server.go
  • transports/config.schema.json
  • ui/app/workspace/config/views/clientSettingsView.tsx
  • ui/lib/types/config.ts
  • ui/lib/types/schemas.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • framework/configstore/clientconfig.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/config.schema.json
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/integrations/router.go
  • framework/configstore/rdb.go
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

always check the stack if there is one for the current PR. do not give localized reviews for the PR, always see all changes in the light of the whole stack of PRs (if there is a stack, if there is no stack you can continue to make localized suggestions/reviews)

Files:

  • ui/lib/types/config.ts
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/server/server.go
  • framework/configstore/tables/clientconfig.go
  • transports/bifrost-http/lib/ctx.go
  • transports/bifrost-http/handlers/inference.go
  • ui/lib/types/schemas.ts
  • framework/configstore/migrations.go
  • transports/bifrost-http/integrations/bedrock_test.go
  • ui/app/workspace/config/views/clientSettingsView.tsx
  • framework/configstore/tables/config.go
🧠 Learnings (2)
📚 Learning: 2025-12-09T17:07:42.007Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/schemas/account.go:9-18
Timestamp: 2025-12-09T17:07:42.007Z
Learning: In core/schemas/account.go, the HuggingFaceKeyConfig field within the Key struct is currently unused and reserved for future Hugging Face inference endpoint deployments. Do not flag this field as missing from OpenAPI documentation or require its presence in the API spec until the feature is actively implemented and used. When the feature is added, update the OpenAPI docs accordingly; otherwise, treat this field as non-breaking and not part of the current API surface.

Applied to files:

  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/server/server.go
  • framework/configstore/tables/clientconfig.go
  • transports/bifrost-http/lib/ctx.go
  • transports/bifrost-http/handlers/inference.go
  • framework/configstore/migrations.go
  • transports/bifrost-http/integrations/bedrock_test.go
  • framework/configstore/tables/config.go
📚 Learning: 2025-12-12T08:25:02.629Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1000
File: transports/bifrost-http/integrations/router.go:709-712
Timestamp: 2025-12-12T08:25:02.629Z
Learning: In transports/bifrost-http/**/*.go, update streaming response handling to align with OpenAI Responses API: use typed SSE events such as response.created, response.output_text.delta, response.done, etc., and do not rely on the legacy data: [DONE] termination marker. Note that data: [DONE] is only used by the older Chat Completions and Text Completions streaming APIs. Ensure parsers, writers, and tests distinguish SSE events from the [DONE] sentinel and handle each event type accordingly for correct stream termination and progress updates.

Applied to files:

  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/server/server.go
  • transports/bifrost-http/lib/ctx.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/integrations/bedrock_test.go
🧬 Code graph analysis (7)
ui/lib/types/config.ts (2)
framework/configstore/tables/config.go (2)
  • GlobalHeaderFilterConfig (46-49)
  • GlobalProxyConfig (25-38)
core/network/http.go (1)
  • GlobalProxyConfig (55-68)
transports/bifrost-http/server/server.go (4)
framework/configstore/tables/config.go (1)
  • GlobalHeaderFilterConfig (46-49)
ui/lib/types/config.ts (1)
  • GlobalHeaderFilterConfig (304-307)
transports/bifrost-http/lib/config.go (1)
  • Config (186-217)
framework/configstore/clientconfig.go (1)
  • ClientConfig (36-51)
framework/configstore/tables/clientconfig.go (2)
framework/configstore/tables/config.go (1)
  • GlobalHeaderFilterConfig (46-49)
ui/lib/types/config.ts (1)
  • GlobalHeaderFilterConfig (304-307)
transports/bifrost-http/lib/ctx.go (2)
framework/configstore/tables/config.go (1)
  • GlobalHeaderFilterConfig (46-49)
ui/lib/types/config.ts (1)
  • GlobalHeaderFilterConfig (304-307)
transports/bifrost-http/handlers/inference.go (1)
transports/bifrost-http/lib/ctx.go (1)
  • ConvertToBifrostContext (80-409)
transports/bifrost-http/integrations/bedrock_test.go (2)
framework/configstore/tables/config.go (1)
  • GlobalHeaderFilterConfig (46-49)
ui/lib/types/config.ts (1)
  • GlobalHeaderFilterConfig (304-307)
framework/configstore/tables/config.go (4)
core/providers/gemini/types.go (1)
  • Type (783-783)
ui/lib/types/config.ts (1)
  • GlobalProxyType (269-269)
core/network/http.go (1)
  • GlobalProxyType (46-46)
core/schemas/provider.go (1)
  • NoProxy (147-147)
🔇 Additional comments (13)
ui/lib/types/config.ts (1)

302-314: Header filter config typing and wiring look consistent with backend.

GlobalHeaderFilterConfig, its default, and CoreConfig.header_filter_config?: GlobalHeaderFilterConfig cleanly mirror the Go GlobalHeaderFilterConfig (allowlist/denylist) and keep the feature optional/backwards‑compatible. No issues from a typing or config‑surface perspective.

Also applies to: 322-349

ui/lib/types/schemas.ts (1)

680-691: Zod schemas for header filter config align with TS/Go types.

globalHeaderFilterConfigSchema and the form wrapper correctly model optional allowlist/denylist arrays, and exported inference types match. Given that security constraints are enforced in the Go handler, keeping this schema simple is appropriate.

Also applies to: 692-710

transports/bifrost-http/integrations/bedrock_test.go (1)

9-10: Mock HandlerStore correctly extended with header filter config.

The added headerFilterConfig field and GetHeaderFilterConfig() method keep mockHandlerStore compatible with the updated lib.HandlerStore interface while defaulting to nil (no filtering) in existing tests. This should compile and behave as before for current test cases.

Also applies to: 17-19, 25-27

framework/configstore/migrations.go (1)

113-115: Header filter config DB migration follows existing patterns and looks safe.

The new migrationAddHeaderFilterConfigJSONColumn is registered in triggerMigrations, conditionally adds header_filter_config_json to TableClientConfig, and cleanly drops it on rollback. It matches the style of other JSON config migrations and should be idempotent across environments.

Also applies to: 1834-1866

transports/bifrost-http/server/server.go (1)

54-67: Server reload hook for header filter config is correctly wired.

Extending ServerCallbacks and implementing ReloadHeaderFilterConfig by updating s.Config.ClientConfig.HeaderFilterConfig and logging list sizes keeps runtime state aligned with the config store/UI without exposing header names. This matches how proxy and other global configs are handled.

Also applies to: 841-856

framework/configstore/tables/config.go (1)

5-13: GlobalHeaderFilterConfig definition matches the intended API surface.

ConfigHeaderFilterKey and GlobalHeaderFilterConfig{Allowlist,Denylist} cleanly define the new config surface and are consistent with both the UI types and runtime usage in lib/ctx.go. JSON tags and comments document the precedence rules clearly.

Also applies to: 40-49

framework/configstore/tables/clientconfig.go (1)

65-73: LGTM! Serialization logic is sound.

The choice to serialize HeaderFilterConfig to empty string "" when nil (vs "[]" for slice fields) is appropriate since GlobalHeaderFilterConfig is a struct, and empty string cleanly represents "no configuration" semantics. The AfterFind hook correctly handles this by only deserializing when non-empty.

transports/bifrost-http/lib/ctx.go (3)

100-116: LGTM! Security denylist is well-designed.

Using a map[string]bool for O(1) lookup is efficient. The security-sensitive headers list is comprehensive and correctly includes authentication, connection, and key-related headers that should never be forwarded.


151-156: Debug logging is appropriate.

Logging the header filter config at debug level is safe since it only contains header name patterns (not values or secrets). Debug logging is typically disabled in production environments.


310-338: Direct header forwarding logic is well-implemented with defense in depth.

The implementation correctly:

  1. Only activates when allowlist is non-empty (backward compatible)
  2. Skips x-bf-* reserved headers
  3. Always enforces security denylist
  4. Respects user-configured denylist even for allowlisted headers

One consideration: the debug log at line 334 includes the header value. While debug logging is typically off in production, consider whether header values could contain sensitive data (e.g., custom tokens) that shouldn't appear in logs.

ui/app/workspace/config/views/clientSettingsView.tsx (3)

115-136: LGTM! Save logic with security validation and cleanup.

Good implementation:

  • Defense-in-depth security header check before save
  • Empty string filtering ensures clean data persistence
  • Non-null assertion on bifrostConfig! is safe since hasChanges depends on config being defined

271-433: Well-designed UI with comprehensive documentation and security feedback.

The Header Forwarding section is well-implemented:

  • Clear accordion-based documentation explaining prefixed vs direct headers
  • Visual feedback (red border) for invalid security headers
  • Consistent RBAC enforcement across all inputs and buttons
  • Helpful placeholder examples

32-46: Verify SECURITY_HEADERS consistency with backend.

The frontend SECURITY_HEADERS list matches the backend securityDenylist in ctx.go (both contain identical headers in the same order). While this duplication currently maintains consistency, it creates a maintenance burden—if either list is updated without updating the other, the frontend validation will diverge from backend enforcement.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants